Version 2.18.0-216.0.dev

Merge commit '3c6720ac68cb824b1cf5da0252ac292cff69bad4' into 'dev'
diff --git a/DEPS b/DEPS
index 84c331d..e0f095f 100644
--- a/DEPS
+++ b/DEPS
@@ -66,8 +66,8 @@
   # The list of revisions for these tools comes from Fuchsia, here:
   # https://fuchsia.googlesource.com/integration/+/HEAD/toolchain
   # If there are problems with the toolchain, contact fuchsia-toolchain@.
-  "clang_revision": "aaaf8e4c409f080f35ea227b20dc6ac8a45c2fa4",
-  "gn_revision": "e62d4e1938a45babc9afb6db543f388cd1802a52",
+  "clang_revision": "c2592c374e469f343ecea82d6728609650924259",
+  "gn_revision": "d7c2209cebcfe37f46dba7be4e1a7000ffc342fb",
 
   # Scripts that make 'git cl format' work.
   "clang_format_scripts_rev": "bb994c6f067340c1135eb43eed84f4b33cfa7397",
@@ -166,7 +166,7 @@
   "webkit_inspection_protocol_rev": "57522d6b29d94903b765c757079d906555d5a171",
   "yaml_edit_rev": "01589b3ce447b03aed991db49f1ec6445ad5476d",
   "yaml_rev": "fda5b15692ccfa0feb7793a27fe3829b3d0f77fa",
-  "zlib_rev": "64bbf988543996eb8df9a86877b32917187eba8f",
+  "zlib_rev": "27c2f474b71d0d20764f86f60ef8b00da1a16cda",
 
   # Windows deps
   "crashpad_rev": "bf327d8ceb6a669607b0dbab5a83a275d03f99ed",
diff --git a/pkg/analysis_server/lib/src/analysis_server_abstract.dart b/pkg/analysis_server/lib/src/analysis_server_abstract.dart
index d199752..acfa949 100644
--- a/pkg/analysis_server/lib/src/analysis_server_abstract.dart
+++ b/pkg/analysis_server/lib/src/analysis_server_abstract.dart
@@ -539,6 +539,9 @@
     // every time the set of plugins changes, in which case we'll need to listen
     // to the `PluginManager.pluginsChanged` stream.
     analyticsManager.changedPlugins(pluginManager);
+    // For now we record context-dependent information only on shutdown. We
+    // might want to record it on start-up as well.
+    analyticsManager.createdAnalysisContexts(contextManager.analysisContexts);
 
     pubPackageService.shutdown();
     analyticsManager.shutdown();
diff --git a/pkg/analysis_server/lib/src/analytics/analytics_manager.dart b/pkg/analysis_server/lib/src/analytics/analytics_manager.dart
index f02612d..8a7f5e2 100644
--- a/pkg/analysis_server/lib/src/analytics/analytics_manager.dart
+++ b/pkg/analysis_server/lib/src/analytics/analytics_manager.dart
@@ -7,6 +7,7 @@
 import 'package:analysis_server/protocol/protocol_generated.dart';
 import 'package:analysis_server/src/analytics/google_analytics_manager.dart';
 import 'package:analysis_server/src/plugin/plugin_manager.dart';
+import 'package:analyzer/dart/analysis/analysis_context.dart';
 import 'package:telemetry/telemetry.dart';
 
 /// An interface for managing and reporting analytics.
@@ -28,6 +29,9 @@
   void changedWorkspaceFolders(
       {required List<String> added, required List<String> removed});
 
+  /// Record that the [contexts] have been created.
+  void createdAnalysisContexts(List<AnalysisContext> contexts);
+
   /// Record that the given [notification] was received and has been handled.
   void handledNotificationMessage(
       {required NotificationMessage notification,
diff --git a/pkg/analysis_server/lib/src/analytics/google_analytics_manager.dart b/pkg/analysis_server/lib/src/analytics/google_analytics_manager.dart
index 2b5f502..d9f99f0 100644
--- a/pkg/analysis_server/lib/src/analytics/google_analytics_manager.dart
+++ b/pkg/analysis_server/lib/src/analytics/google_analytics_manager.dart
@@ -11,6 +11,7 @@
 import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
 import 'package:analysis_server/src/plugin/plugin_manager.dart';
 import 'package:analysis_server/src/protocol_server.dart';
+import 'package:analyzer/dart/analysis/analysis_context.dart';
 import 'package:telemetry/telemetry.dart';
 
 /// An implementation of [AnalyticsManager] that's appropriate to use when
@@ -36,6 +37,10 @@
   /// that have been handled.
   final Map<String, _NotificationData> _completedNotifications = {};
 
+  /// A map from the name of a lint to the number of contexts in which the lint
+  /// was enabled.
+  final Map<String, int> _lintUsageCounts = {};
+
   /// Initialize a newly created analytics manager to report to the [analytics]
   /// service.
   GoogleAnalyticsManager(this.analytics);
@@ -55,6 +60,22 @@
   }
 
   @override
+  void createdAnalysisContexts(List<AnalysisContext> contexts) {
+    for (var context in contexts) {
+      for (var rule in context.analysisOptions.lintRules) {
+        var name = rule.name;
+        _lintUsageCounts[name] = (_lintUsageCounts[name] ?? 0) + 1;
+      }
+      // TODO(brianwilkerson) Collect other context-dependent information, such
+      //  as which codes have a different severity assigned to them:
+      // for (var processor in context.analysisOptions.errorProcessors) {
+      //   processor.code;
+      //   processor.severity;
+      // }
+    }
+  }
+
+  @override
   void handledNotificationMessage(
       {required NotificationMessage notification,
       required DateTime startTime,
@@ -118,6 +139,7 @@
     _sendServerResponseTimes();
     _sendPluginResponseTimes();
     _sendNotificationHandlingTimes();
+    _sendLintUsageCounts();
 
     analytics.waitForLastPing(timeout: Duration(milliseconds: 200)).then((_) {
       analytics.close();
@@ -204,6 +226,12 @@
     requestData.responseTimes.addValue(responseTime);
   }
 
+  void _sendLintUsageCounts() {
+    analytics.sendEvent('language_server', 'lintUsageCounts', parameters: {
+      'usageCounts': _lintUsageCounts.toString(),
+    });
+  }
+
   /// Send information about the notifications handled by the server.
   void _sendNotificationHandlingTimes() {
     for (var data in _completedNotifications.values) {
diff --git a/pkg/analysis_server/lib/src/analytics/noop_analytics_manager.dart b/pkg/analysis_server/lib/src/analytics/noop_analytics_manager.dart
index 3f6c283..169bb79 100644
--- a/pkg/analysis_server/lib/src/analytics/noop_analytics_manager.dart
+++ b/pkg/analysis_server/lib/src/analytics/noop_analytics_manager.dart
@@ -7,6 +7,7 @@
 import 'package:analysis_server/protocol/protocol_generated.dart';
 import 'package:analysis_server/src/analytics/analytics_manager.dart';
 import 'package:analysis_server/src/plugin/plugin_manager.dart';
+import 'package:analyzer/dart/analysis/analysis_context.dart';
 
 /// An implementation of [AnalyticsManager] that's appropriate to use when
 /// analytics have not been enabled.
@@ -19,6 +20,9 @@
       {required List<String> added, required List<String> removed}) {}
 
   @override
+  void createdAnalysisContexts(List<AnalysisContext> contexts) {}
+
+  @override
   void handledNotificationMessage(
       {required NotificationMessage notification,
       required DateTime startTime,
diff --git a/pkg/analysis_server/lib/src/context_manager.dart b/pkg/analysis_server/lib/src/context_manager.dart
index 47748d6..f6beb54 100644
--- a/pkg/analysis_server/lib/src/context_manager.dart
+++ b/pkg/analysis_server/lib/src/context_manager.dart
@@ -7,6 +7,7 @@
 
 import 'package:analysis_server/src/lsp/handlers/handlers.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/transform_set_parser.dart';
+import 'package:analyzer/dart/analysis/analysis_context.dart';
 import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/file_system/file_system.dart';
@@ -46,6 +47,9 @@
 /// Class that maintains a mapping from included/excluded paths to a set of
 /// folders that should correspond to analysis contexts.
 abstract class ContextManager {
+  /// Return the analysis contexts that are currently defined.
+  List<AnalysisContext> get analysisContexts;
+
   /// Get the callback interface used to create, destroy, and update contexts.
   ContextManagerCallbacks get callbacks;
 
@@ -237,6 +241,10 @@
   }
 
   @override
+  List<AnalysisContext> get analysisContexts =>
+      _collection?.contexts.cast<AnalysisContext>() ?? const [];
+
+  @override
   DriverBasedAnalysisContext? getContextFor(String path) {
     try {
       return _collection?.contextFor(path);
diff --git a/pkg/analysis_server/test/src/analytics/google_analytics_manager_test.dart b/pkg/analysis_server/test/src/analytics/google_analytics_manager_test.dart
index 3542489..32a5783 100644
--- a/pkg/analysis_server/test/src/analytics/google_analytics_manager_test.dart
+++ b/pkg/analysis_server/test/src/analytics/google_analytics_manager_test.dart
@@ -10,7 +10,9 @@
 import 'package:analysis_server/src/analytics/percentile_calculator.dart';
 import 'package:analysis_server/src/plugin/plugin_manager.dart';
 import 'package:analysis_server/src/protocol_server.dart';
+import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
 import 'package:analyzer/dart/analysis/context_root.dart' as analyzer;
+import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:telemetry/telemetry.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -22,10 +24,31 @@
 }
 
 @reflectiveTest
-class GoogleAnalyticsManagerTest {
+class GoogleAnalyticsManagerTest with ResourceProviderMixin {
   final analytics = _MockAnalytics();
   late final manager = GoogleAnalyticsManager(analytics);
 
+  String get testPackageRootPath => '/home/package';
+
+  void test_createAnalysisContexts_single() {
+    _createAnalysisOptionsFile(lints: [
+      'avoid_dynamic_calls',
+      'await_only_futures',
+      'unawaited_futures'
+    ]);
+    var collection =
+        AnalysisContextCollection(includedPaths: [testPackageRootPath]);
+    _defaultStartup();
+    manager.createdAnalysisContexts(collection.contexts);
+    manager.shutdown();
+    analytics.assertEvents([
+      _ExpectedEvent.session(),
+      _ExpectedEvent.lintUsageCounts(parameters: {
+        'usageCounts': '{}',
+      }),
+    ]);
+  }
+
   void test_plugin_request() {
     _defaultStartup();
     PluginManager.pluginResponseTimes[_MockPluginInfo('a')] = {
@@ -39,6 +62,7 @@
         'method': 'analysis.getNavigation',
         'duration': _IsPercentiles(),
       }),
+      _ExpectedEvent.lintUsageCounts(),
     ]);
     PluginManager.pluginResponseTimes.clear();
   }
@@ -60,6 +84,7 @@
         'method': Method.workspace_didCreateFiles.toString(),
         'duration': _IsPercentiles(),
       }),
+      _ExpectedEvent.lintUsageCounts(),
     ]);
   }
 
@@ -89,6 +114,7 @@
         'removed':
             '{"count":1,"percentiles":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]}',
       }),
+      _ExpectedEvent.lintUsageCounts(),
     ]);
   }
 
@@ -112,6 +138,7 @@
         ANALYSIS_REQUEST_SET_ANALYSIS_ROOTS_EXCLUDED:
             '{"count":1,"percentiles":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]}',
       }),
+      _ExpectedEvent.lintUsageCounts(),
     ]);
   }
 
@@ -133,6 +160,7 @@
         ANALYSIS_REQUEST_SET_PRIORITY_FILES_FILES:
             '{"count":1,"percentiles":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]}',
       }),
+      _ExpectedEvent.lintUsageCounts(),
     ]);
   }
 
@@ -153,6 +181,7 @@
         'duration': _IsPercentiles(),
         EDIT_REQUEST_GET_REFACTORING_KIND: '{"RENAME":1}',
       }),
+      _ExpectedEvent.lintUsageCounts(),
     ]);
   }
 
@@ -172,6 +201,7 @@
         'parameters':
             'closingLabels,onlyAnalyzeProjectsWithOpenFiles,suggestFromUnimportedLibraries',
       }),
+      _ExpectedEvent.lintUsageCounts(),
     ]);
   }
 
@@ -197,6 +227,7 @@
         'openWorkspacePaths':
             '{"count":1,"percentiles":[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]}',
       }),
+      _ExpectedEvent.lintUsageCounts(),
     ]);
   }
 
@@ -213,6 +244,7 @@
         'method': SERVER_REQUEST_SHUTDOWN,
         'duration': _IsPercentiles(),
       }),
+      _ExpectedEvent.lintUsageCounts(),
     ]);
   }
 
@@ -240,6 +272,7 @@
         'sdkVersion': sdkVersion,
         'duration': _IsStringEncodedPositiveInt(),
       }),
+      _ExpectedEvent.lintUsageCounts(),
     ]);
   }
 
@@ -256,6 +289,7 @@
       _ExpectedEvent.session(parameters: {
         'plugins': '{"recordCount":1,"rootCounts":{"a":$counts,"b":$counts}}'
       }),
+      _ExpectedEvent.lintUsageCounts(),
     ]);
   }
 
@@ -280,9 +314,47 @@
         'sdkVersion': sdkVersion,
         'duration': _IsStringEncodedPositiveInt(),
       }),
+      _ExpectedEvent.lintUsageCounts(),
     ]);
   }
 
+  /// Create an analysis options file based on the given arguments.
+  void _createAnalysisOptionsFile({
+    String? path,
+    List<String>? experiments,
+    bool? implicitCasts,
+    List<String>? lints,
+  }) {
+    path ??= '$testPackageRootPath/analysis_options.yaml';
+    var buffer = StringBuffer();
+
+    if (experiments != null || implicitCasts != null) {
+      buffer.writeln('analyzer:');
+    }
+
+    if (experiments != null) {
+      buffer.writeln('  enable-experiment:');
+      for (var experiment in experiments) {
+        buffer.writeln('    - $experiment');
+      }
+    }
+
+    if (implicitCasts != null) {
+      buffer.writeln('  strong-mode:');
+      buffer.writeln('    implicit-casts: $implicitCasts');
+    }
+
+    if (lints != null) {
+      buffer.writeln('linter:');
+      buffer.writeln('  rules:');
+      for (var lint in lints) {
+        buffer.writeln('    - $lint');
+      }
+    }
+
+    newFile(path, buffer.toString());
+  }
+
   void _defaultStartup() {
     manager.startUp(
         time: DateTime.now(),
@@ -319,6 +391,9 @@
       this.value, // ignore: unused_element
       this.parameters});
 
+  _ExpectedEvent.lintUsageCounts({Map<String, Object>? parameters})
+      : this('language_server', 'lintUsageCounts', parameters: parameters);
+
   _ExpectedEvent.notification({Map<String, Object>? parameters})
       : this('language_server', 'notification', parameters: parameters);
 
diff --git a/pkg/analysis_server/test/src/cider/cider_service.dart b/pkg/analysis_server/test/src/cider/cider_service.dart
index cdfefa6..6ebf3aa 100644
--- a/pkg/analysis_server/test/src/cider/cider_service.dart
+++ b/pkg/analysis_server/test/src/cider/cider_service.dart
@@ -15,6 +15,8 @@
 import 'package:linter/src/rules.dart';
 
 class CiderServiceTest with ResourceProviderMixin {
+  final FileResolverTestData testData = FileResolverTestData();
+
   final StringBuffer logBuffer = StringBuffer();
   late PerformanceLog logger;
 
@@ -45,8 +47,8 @@
       workspace: workspace,
       byteStore: MemoryByteStore(),
       isGenerated: (_) => false,
+      testData: testData,
     );
-    fileResolver.testView = FileResolverTestView();
   }
 
   void setUp() {
diff --git a/pkg/analysis_server/test/src/cider/fixes_test.dart b/pkg/analysis_server/test/src/cider/fixes_test.dart
index 66f85fa..8eac9aa2 100644
--- a/pkg/analysis_server/test/src/cider/fixes_test.dart
+++ b/pkg/analysis_server/test/src/cider/fixes_test.dart
@@ -48,7 +48,7 @@
 ''');
 
     // The file was resolved only once, even though we have 2 errors.
-    expect(fileResolver.testView!.resolvedLibraries, [convertPath(testPath)]);
+    expect(testData.resolvedLibraries, [convertPath(testPath)]);
   }
 
   Future<void> test_createMethod() async {
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart
index f64c69e..fc9a76e 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart
@@ -1998,7 +1998,7 @@
     toggleableActiveColor: Colors.black,
     hoverColor: Colors.white,
     primaryColor: Colors.white,
-    sliderTheme: SliderThemeData(),
+    sliderTheme: SliderThemeData()
   );
   print(themeData);
 }
@@ -2012,7 +2012,7 @@
     focusColor: Colors.black,
     hoverColor: Colors.white,
     primaryColor: Colors.white,
-    sliderTheme: SliderThemeData(), checkboxTheme: CheckboxThemeData(), radioTheme: RadioThemeData(),
+    sliderTheme: SliderThemeData(), checkboxTheme: CheckboxThemeData(), radioTheme: RadioThemeData()
   );
   print(themeData);
 }
@@ -2183,7 +2183,7 @@
     toggleableActiveColor: Colors.black,
     hoverColor: Colors.white,
     primaryColor: Colors.white,
-    sliderTheme: SliderThemeData(),
+    sliderTheme: SliderThemeData()
   );
   print(themeData);
 }
@@ -2197,7 +2197,7 @@
     focusColor: Colors.black,
     hoverColor: Colors.white,
     primaryColor: Colors.white,
-    sliderTheme: SliderThemeData(), checkboxTheme: CheckboxThemeData(), radioTheme: RadioThemeData(),
+    sliderTheme: SliderThemeData(), checkboxTheme: CheckboxThemeData(), radioTheme: RadioThemeData()
   );
   print(themeData);
 }
diff --git a/pkg/analyzer/lib/src/dart/analysis/context_builder.dart b/pkg/analyzer/lib/src/dart/analysis/context_builder.dart
index 2c0bac1..d796e2b 100644
--- a/pkg/analyzer/lib/src/dart/analysis/context_builder.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/context_builder.dart
@@ -15,7 +15,7 @@
 import 'package:analyzer/src/dart/analysis/byte_store.dart'
     show ByteStore, MemoryByteStore;
 import 'package:analyzer/src/dart/analysis/driver.dart'
-    show AnalysisDriver, AnalysisDriverScheduler;
+    show AnalysisDriver, AnalysisDriverScheduler, AnalysisDriverTestView;
 import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
 import 'package:analyzer/src/dart/analysis/file_content_cache.dart';
 import 'package:analyzer/src/dart/analysis/performance_logger.dart'
@@ -145,6 +145,7 @@
       macroKernelBuilder: macroKernelBuilder,
       macroExecutor: macroExecutor,
       declaredVariables: declaredVariables,
+      testView: retainDataForTesting ? AnalysisDriverTestView() : null,
     );
 
     // AnalysisDriver reports results into streams.
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index f80867d..5f5d2d2 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -48,7 +48,6 @@
 import 'package:analyzer/src/summary2/package_bundle_format.dart';
 import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:analyzer/src/util/performance/operation_performance.dart';
-import 'package:meta/meta.dart';
 
 /// This class computes [AnalysisResult]s for Dart files.
 ///
@@ -217,7 +216,7 @@
   /// The instance of the [Search] helper.
   late final Search _search;
 
-  late final AnalysisDriverTestView _testView;
+  final AnalysisDriverTestView? testView;
 
   late FeatureSetProvider featureSetProvider;
 
@@ -264,6 +263,7 @@
     SummaryDataStore? externalSummaries,
     DeclaredVariables? declaredVariables,
     bool retainDataForTesting = false,
+    this.testView,
   })  : _scheduler = scheduler,
         _resourceProvider = resourceProvider,
         _byteStore = byteStore,
@@ -277,8 +277,8 @@
         declaredVariables = declaredVariables ?? DeclaredVariables(),
         testingData = retainDataForTesting ? TestingData() : null {
     analysisContext?.driver = this;
+    testView?.driver = this;
     _onResults = _resultController.stream.asBroadcastStream();
-    _testView = AnalysisDriverTestView(this);
 
     _fileContentStrategy = StoredFileContentStrategy(_fileContentCache);
 
@@ -322,7 +322,7 @@
   /// Return the context in which libraries should be analyzed.
   LibraryContext get libraryContext {
     return _libraryContext ??= LibraryContext(
-      testView: _testView.libraryContextTestView,
+      testData: testView?.libraryContext,
       analysisSession: AnalysisSessionImpl(this),
       logger: _logger,
       byteStore: _byteStore,
@@ -395,9 +395,6 @@
   /// from file paths.
   SourceFactory get sourceFactory => _sourceFactory;
 
-  @visibleForTesting
-  AnalysisDriverTestView get test => _testView;
-
   @override
   AnalysisDriverPriority get workPriority {
     if (_resolveForCompletionRequests.isNotEmpty) {
@@ -567,7 +564,6 @@
   /// library that was resynthesized, but after some initial analysis we might
   /// not get again to many of these libraries. So, we should clear the context
   /// periodically.
-  @visibleForTesting
   void clearLibraryContext() {
     _libraryContext?.dispose();
     _libraryContext = null;
@@ -1244,6 +1240,9 @@
           break;
         case _FileChangeKind.remove:
           _fileTracker.removeFile(path);
+          // TODO(scheglov) We have to do this because we discard files.
+          // But this is not right, we need to handle removing better.
+          clearLibraryContext();
           break;
       }
     }
@@ -1313,7 +1312,7 @@
     return _logger.runAsync('Compute analysis result for $path', () async {
       _logger.writeln('Work in $name');
       try {
-        _testView.numOfAnalyzedLibraries++;
+        testView?.numOfAnalyzedLibraries++;
 
         if (!_hasLibraryByUri('dart:core')) {
           return _newMissingDartLibraryResult(file, 'dart:core');
@@ -1323,7 +1322,10 @@
           return _newMissingDartLibraryResult(file, 'dart:async');
         }
 
-        await libraryContext.load(library);
+        await libraryContext.load(
+          targetLibrary: library,
+          performance: OperationPerformanceImpl('<root>'),
+        );
 
         var results = LibraryAnalyzer(
           analysisOptions as AnalysisOptionsImpl,
@@ -1386,8 +1388,11 @@
   ) async {
     final path = library.file.path;
     return _logger.runAsync('Compute resolved library $path', () async {
-      _testView.numOfAnalyzedLibraries++;
-      await libraryContext.load(library);
+      testView?.numOfAnalyzedLibraries++;
+      await libraryContext.load(
+        targetLibrary: library,
+        performance: OperationPerformanceImpl('<root>'),
+      );
 
       var unitResults = LibraryAnalyzer(
               analysisOptions as AnalysisOptionsImpl,
@@ -1434,7 +1439,10 @@
 
     return _logger.runAsync('Compute unit element for $path', () async {
       _logger.writeln('Work in $name');
-      await libraryContext.load(library);
+      await libraryContext.load(
+        targetLibrary: library,
+        performance: OperationPerformanceImpl('<root>'),
+      );
       var element = libraryContext.computeUnitElement(library, file);
       return UnitElementResultImpl(
         currentSession,
@@ -1479,6 +1487,7 @@
       fileContentStrategy: _fileContentStrategy,
       prefetchFiles: null,
       isGenerated: (_) => false,
+      testData: testView?.fileSystem,
     );
     _fileTracker = FileTracker(_logger, _fsState, _fileContentStrategy);
   }
@@ -1667,15 +1676,14 @@
     var affected = <FileState>{};
     _fsState.collectAffected(path, affected);
 
+    final removedKeys = <String>{};
+    _libraryContext?.remove(affected.toList(), removedKeys);
+
     for (var file in affected) {
       file.invalidateLibraryCycle();
       accumulatedAffected.add(file.path);
     }
 
-    _libraryContext?.elementFactory.removeLibraries(
-      affected.map((e) => e.uri).toSet(),
-    );
-
     _libraryContext?.elementFactory.replaceAnalysisSession(
       AnalysisSessionImpl(this),
     );
@@ -1739,7 +1747,10 @@
     final kind = file.kind;
     final library = kind.library ?? kind.asLibrary;
 
-    await libraryContext.load(library);
+    await libraryContext.load(
+      targetLibrary: library,
+      performance: OperationPerformanceImpl('<root>'),
+    );
     var unitElement = libraryContext.computeUnitElement(library, file);
 
     var analysisResult = LibraryAnalyzer(
@@ -2097,16 +2108,14 @@
   }
 }
 
-@visibleForTesting
 class AnalysisDriverTestView {
-  final AnalysisDriver driver;
-  final LibraryContextTestView libraryContextTestView =
-      LibraryContextTestView();
+  final fileSystem = FileSystemTestData();
+  final libraryContext = LibraryContextTestData();
+
+  late final AnalysisDriver driver;
 
   int numOfAnalyzedLibraries = 0;
 
-  AnalysisDriverTestView(this.driver);
-
   FileTracker get fileTracker => driver._fileTracker;
 
   Set<String> get loadedLibraryUriSet {
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index 8ce8e30..34b9105 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -1144,7 +1144,7 @@
 
   late final FileSystemStateTestView _testView;
 
-  FileSystemTestData? testData;
+  final FileSystemTestData? testData;
 
   FileSystemState(
     this._logger,
@@ -1162,6 +1162,7 @@
     required this.fileContentStrategy,
     required this.prefetchFiles,
     required this.isGenerated,
+    required this.testData,
   }) {
     _testView = FileSystemStateTestView(this);
   }
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_context.dart b/pkg/analyzer/lib/src/dart/analysis/library_context.dart
index 9f4aeb5..8a81e56 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_context.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_context.dart
@@ -2,6 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:collection';
 import 'dart:typed_data';
 
 import 'package:_fe_analyzer_shared/src/macros/executor/multi_executor.dart'
@@ -9,6 +10,7 @@
 import 'package:analyzer/dart/analysis/declared_variables.dart';
 import 'package:analyzer/dart/element/element.dart'
     show CompilationUnitElement, LibraryElement;
+import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/context/context.dart';
 import 'package:analyzer/src/dart/analysis/byte_store.dart';
 import 'package:analyzer/src/dart/analysis/driver.dart';
@@ -28,6 +30,8 @@
 import 'package:analyzer/src/summary2/macro.dart';
 import 'package:analyzer/src/summary2/reference.dart';
 import 'package:analyzer/src/util/performance/operation_performance.dart';
+import 'package:analyzer/src/utilities/extensions/collection.dart';
+import 'package:collection/collection.dart';
 import 'package:path/src/context.dart';
 
 /// Context information necessary to analyze one or more libraries within an
@@ -35,7 +39,7 @@
 ///
 /// Currently this is implemented as a wrapper around [AnalysisContext].
 class LibraryContext {
-  final LibraryContextTestView testView;
+  final LibraryContextTestData? testData;
   final PerformanceLog logger;
   final ByteStore byteStore;
   final FileSystemState fileSystemState;
@@ -46,8 +50,10 @@
   late final AnalysisContextImpl analysisContext;
   late LinkedElementFactory elementFactory;
 
+  Set<LibraryCycle> loadedBundles = Set.identity();
+
   LibraryContext({
-    required this.testView,
+    required this.testData,
     required AnalysisSessionImpl analysisSession,
     required this.logger,
     required this.byteStore,
@@ -96,8 +102,13 @@
     return element as CompilationUnitElementImpl;
   }
 
-  void dispose() {
+  /// Notifies this object that it is about to be discarded.
+  ///
+  /// Returns the keys of the artifacts that are no longer used.
+  Set<String> dispose() {
+    final keys = unloadAll();
     elementFactory.dispose();
+    return keys;
   }
 
   /// Get the [LibraryElement] for the given library.
@@ -107,7 +118,10 @@
   }
 
   /// Load data required to access elements of the given [targetLibrary].
-  Future<void> load(LibraryFileStateKind targetLibrary) async {
+  Future<void> load({
+    required LibraryFileStateKind targetLibrary,
+    required OperationPerformanceImpl performance,
+  }) async {
     var librariesTotal = 0;
     var librariesLoaded = 0;
     var librariesLinked = 0;
@@ -117,10 +131,10 @@
     var bytesPut = 0;
 
     Future<void> loadBundle(LibraryCycle cycle) async {
-      if (cycle.libraries.isEmpty ||
-          elementFactory.hasLibrary(cycle.libraries.first.uri)) {
-        return;
-      }
+      if (!loadedBundles.add(cycle)) return;
+
+      performance.getDataInt('cycleCount').increment();
+      performance.getDataInt('libraryCount').add(cycle.libraries.length);
 
       librariesTotal += cycle.libraries.length;
 
@@ -160,7 +174,7 @@
       if (resolutionBytes == null) {
         librariesLinkedTimer.start();
 
-        testView.linkedCycles.add(
+        testData?.linkedCycles.add(
           cycle.libraries.map((e) => e.path).toSet(),
         );
 
@@ -175,6 +189,9 @@
             var isSynthetic = !file.exists;
             var unit = file.parse();
 
+            performance.getDataInt('parseCount').increment();
+            performance.getDataInt('parseLength').add(unit.length);
+
             String? partUriStr;
             if (partIndex >= 0) {
               partUriStr = libraryFile.unlinked2.parts[partIndex];
@@ -218,10 +235,15 @@
 
         resolutionBytes = linkResult.resolutionBytes;
         byteStore.putGet(resolutionKey, resolutionBytes);
+        performance.getDataInt('bytesPut').add(resolutionBytes.length);
+        testData?.forCycle(cycle).putKeys.add(resolutionKey);
         bytesPut += resolutionBytes.length;
 
         librariesLinkedTimer.stop();
       } else {
+        testData?.forCycle(cycle).getKeys.add(resolutionKey);
+        performance.getDataInt('bytesGet').add(resolutionBytes.length);
+        performance.getDataInt('libraryLoadCount').add(cycle.libraries.length);
         // TODO(scheglov) Take / clear parsed units in files.
         bytesGet += resolutionBytes.length;
         librariesLoaded += cycle.libraries.length;
@@ -233,6 +255,8 @@
           ),
         );
       }
+      // TODO(scheglov) We probably should set this key when create the cycle
+      cycle.resolutionKey = resolutionKey;
 
       final macroKernelBuilder = this.macroKernelBuilder;
       if (macroKernelBuilder != null && macroLibraries.isNotEmpty) {
@@ -284,6 +308,42 @@
     _createElementFactoryTypeProvider();
   }
 
+  /// Remove libraries represented by the [removed] files.
+  /// If we need these libraries later, we will relink and reattach them.
+  void remove(List<FileState> removed, Set<String> removedKeys) {
+    elementFactory.removeLibraries(
+      removed.map((e) => e.uri).toSet(),
+    );
+
+    final removedSet = removed.toSet();
+
+    loadedBundles.removeWhere((cycle) {
+      if (cycle.libraries.any(removedSet.contains)) {
+        removedKeys.addIfNotNull(cycle.resolutionKey);
+        return true;
+      }
+      return false;
+    });
+  }
+
+  /// Unloads all loaded bundles.
+  ///
+  /// Returns the keys of the artifacts that are no longer used.
+  Set<String> unloadAll() {
+    final keySet = <String>{};
+    final uriSet = <Uri>{};
+
+    for (final cycle in loadedBundles) {
+      keySet.addIfNotNull(cycle.resolutionKey);
+      uriSet.addAll(cycle.libraries.map((e) => e.uri));
+    }
+
+    elementFactory.removeLibraries(uriSet);
+    loadedBundles.clear();
+
+    return keySet;
+  }
+
   /// Ensure that type provider is created.
   void _createElementFactoryTypeProvider() {
     if (!analysisContext.hasTypeProvider) {
@@ -312,8 +372,27 @@
   }
 }
 
-class LibraryContextTestView {
+class LibraryContextTestData {
+  /// TODO(scheglov) Use [libraryCycles] and textual dumps for the driver too.
   final List<Set<String>> linkedCycles = [];
+
+  /// Keys: the sorted list of library files.
+  final Map<List<File>, LibraryCycleTestData> libraryCycles = LinkedHashMap(
+    hashCode: Object.hashAll,
+    equals: const ListEquality<File>().equals,
+  );
+
+  LibraryCycleTestData forCycle(LibraryCycle cycle) {
+    final files = cycle.libraries.map((e) => e.resource).toList();
+    files.sortBy((file) => file.path);
+
+    return libraryCycles[files] ??= LibraryCycleTestData();
+  }
+}
+
+class LibraryCycleTestData {
+  final List<String> getKeys = [];
+  final List<String> putKeys = [];
 }
 
 class _MacroFileEntry implements MacroFileEntry {
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
index 89f3f5f..1fbd07c 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
@@ -19,6 +19,9 @@
 
 /// Information about libraries that reference each other, so form a cycle.
 class LibraryCycle {
+  static int _nextId = 0;
+  final int id = _nextId++;
+
   /// The libraries that belong to this cycle.
   final List<FileState> libraries;
 
@@ -118,7 +121,7 @@
 
   @override
   String toString() {
-    return '[${libraries.join(', ')}]';
+    return '[$id][${libraries.join(', ')}]';
   }
 }
 
diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart
index 765ffc5..a2bca9a 100644
--- a/pkg/analyzer/lib/src/dart/element/element.dart
+++ b/pkg/analyzer/lib/src/dart/element/element.dart
@@ -3649,7 +3649,7 @@
 class LibraryAugmentationElementImpl extends LibraryOrAugmentationElementImpl
     implements LibraryAugmentationElement {
   @override
-  final LibraryOrAugmentationElement augmented;
+  final LibraryOrAugmentationElementImpl augmented;
 
   LibraryAugmentationElementImpl({
     required this.augmented,
@@ -3677,7 +3677,7 @@
   Scope get scope => throw UnimplementedError();
 
   @override
-  AnalysisSession get session => augmented.session;
+  AnalysisSessionImpl get session => augmented.session;
 
   @override
   TypeProvider get typeProvider => augmented.typeProvider;
@@ -4231,6 +4231,9 @@
       _prefixes ??= buildPrefixesFromImports(imports);
 
   @override
+  AnalysisSessionImpl get session;
+
+  @override
   Source get source {
     return _definingCompilationUnit.source;
   }
@@ -5098,8 +5101,8 @@
       super.enclosingElement as LibraryElement;
 
   @override
-  LibraryOrAugmentationElement get enclosingElement2 =>
-      super.enclosingElement as LibraryOrAugmentationElement;
+  LibraryOrAugmentationElementImpl get enclosingElement2 =>
+      super.enclosingElement as LibraryOrAugmentationElementImpl;
 
   @override
   List<ImportElement> get imports {
diff --git a/pkg/analyzer/lib/src/dart/element/scope.dart b/pkg/analyzer/lib/src/dart/element/scope.dart
index 259ad2c..f57b6e0 100644
--- a/pkg/analyzer/lib/src/dart/element/scope.dart
+++ b/pkg/analyzer/lib/src/dart/element/scope.dart
@@ -108,10 +108,10 @@
 }
 
 class LibraryScope extends EnclosedScope {
-  final LibraryElement _element;
+  final LibraryElementImpl _element;
   final List<ExtensionElement> extensions = [];
 
-  LibraryScope(LibraryElement element)
+  LibraryScope(LibraryElementImpl element)
       : _element = element,
         super(_LibraryImportScope(element)) {
     extensions.addAll((_parent as _LibraryImportScope).extensions);
@@ -147,19 +147,18 @@
 }
 
 class PrefixScope implements Scope {
-  final LibraryOrAugmentationElement _library;
+  final LibraryOrAugmentationElementImpl _library;
   final Map<String, ImportedElement> _getters = {};
   final Map<String, ImportedElement> _setters = {};
   final Set<ExtensionElement> _extensions = {};
   LibraryElement? _deferredLibrary;
 
   PrefixScope(this._library, PrefixElement? prefix) {
-    for (var import in _library.imports) {
+    final elementFactory = _library.session.elementFactory;
+    for (final import in _library.imports) {
       if (import.prefix == prefix) {
         final importedLibrary = import.importedLibrary;
         if (importedLibrary is LibraryElementImpl) {
-          // TODO(scheglov) Ask it from `_library`.
-          final elementFactory = importedLibrary.session.elementFactory;
           final combinators = import.combinators.build();
           for (final exportedReference in importedLibrary.exportedReferences) {
             final reference = exportedReference.reference;
@@ -330,11 +329,11 @@
 }
 
 class _LibraryImportScope implements Scope {
-  final LibraryElement _library;
+  final LibraryElementImpl _library;
   final PrefixScope _nullPrefixScope;
   List<ExtensionElement>? _extensions;
 
-  _LibraryImportScope(LibraryElement library)
+  _LibraryImportScope(LibraryElementImpl library)
       : _library = library,
         _nullPrefixScope = PrefixScope(library, null);
 
diff --git a/pkg/analyzer/lib/src/dart/micro/analysis_context.dart b/pkg/analyzer/lib/src/dart/micro/analysis_context.dart
index 7f33467..887f483 100644
--- a/pkg/analyzer/lib/src/dart/micro/analysis_context.dart
+++ b/pkg/analyzer/lib/src/dart/micro/analysis_context.dart
@@ -10,7 +10,6 @@
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/analysis/uri_converter.dart';
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/context/context.dart';
 import 'package:analyzer/src/dart/analysis/context_root.dart';
 import 'package:analyzer/src/dart/analysis/driver.dart';
 import 'package:analyzer/src/dart/analysis/results.dart';
@@ -20,7 +19,6 @@
 import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/summary2/linked_element_factory.dart';
-import 'package:analyzer/src/summary2/reference.dart';
 
 MicroContextObjects createMicroContextObjects({
   required FileResolver fileResolver,
@@ -31,12 +29,6 @@
 }) {
   var declaredVariables = DeclaredVariables();
 
-  var analysisContext = AnalysisContextImpl(
-    analysisOptions: analysisOptions,
-    declaredVariables: declaredVariables,
-    sourceFactory: sourceFactory,
-  );
-
   var analysisSession = _MicroAnalysisSessionImpl(
     fileResolver,
     declaredVariables,
@@ -55,34 +47,24 @@
   analysisContext2.currentSession = analysisSession;
   analysisSession.analysisContext = analysisContext2;
 
-  analysisSession.elementFactory = LinkedElementFactory(
-    analysisContext,
-    analysisSession,
-    Reference.root(),
-  );
-
   return MicroContextObjects._(
     declaredVariables: declaredVariables,
+    analysisOptions: analysisOptions,
     analysisSession: analysisSession,
-    analysisContext: analysisContext,
   );
 }
 
 class MicroContextObjects {
   final DeclaredVariables declaredVariables;
+  final AnalysisOptionsImpl analysisOptions;
   final _MicroAnalysisSessionImpl analysisSession;
-  final AnalysisContextImpl analysisContext;
 
   MicroContextObjects._({
     required this.declaredVariables,
+    required this.analysisOptions,
     required this.analysisSession,
-    required this.analysisContext,
   });
 
-  set analysisOptions(AnalysisOptionsImpl analysisOptions) {
-    analysisContext.analysisOptions = analysisOptions;
-  }
-
   InheritanceManager3 get inheritanceManager {
     return analysisSession.inheritanceManager;
   }
diff --git a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
index b42bdf8..cb41ad2 100644
--- a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
+++ b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
@@ -2,14 +2,12 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:collection';
 import 'dart:typed_data';
 
 import 'package:analyzer/dart/analysis/declared_variables.dart';
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/error/error.dart';
-import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/source/line_info.dart';
 import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
@@ -21,7 +19,7 @@
 import 'package:analyzer/src/dart/analysis/experiments.dart';
 import 'package:analyzer/src/dart/analysis/feature_set_provider.dart';
 import 'package:analyzer/src/dart/analysis/file_state.dart';
-import 'package:analyzer/src/dart/analysis/library_graph.dart';
+import 'package:analyzer/src/dart/analysis/library_context.dart';
 import 'package:analyzer/src/dart/analysis/performance_logger.dart';
 import 'package:analyzer/src/dart/analysis/results.dart';
 import 'package:analyzer/src/dart/analysis/search.dart';
@@ -33,12 +31,9 @@
 import 'package:analyzer/src/summary/api_signature.dart';
 import 'package:analyzer/src/summary/format.dart';
 import 'package:analyzer/src/summary/idl.dart';
-import 'package:analyzer/src/summary2/bundle_reader.dart';
-import 'package:analyzer/src/summary2/link.dart';
-import 'package:analyzer/src/summary2/linked_element_factory.dart';
+import 'package:analyzer/src/summary/package_bundle_reader.dart';
 import 'package:analyzer/src/task/options.dart';
 import 'package:analyzer/src/util/performance/operation_performance.dart';
-import 'package:analyzer/src/utilities/extensions/collection.dart';
 import 'package:analyzer/src/utilities/extensions/file_system.dart';
 import 'package:analyzer/src/workspace/workspace.dart';
 import 'package:collection/collection.dart';
@@ -177,7 +172,7 @@
   final Workspace workspace;
 
   /// This field gets value only during testing.
-  FileResolverTestView? testView;
+  final FileResolverTestData? testData;
 
   FileSystemState? fsState;
 
@@ -211,6 +206,7 @@
     required this.workspace,
     required this.isGenerated,
     required this.byteStore,
+    this.testData,
   });
 
   /// Update the resolver to reflect the fact that the files with the given
@@ -421,7 +417,7 @@
 
     await performance.runAsync('libraryContext', (performance) async {
       await libraryContext!.load(
-        targetLibrary: file,
+        targetLibrary: kind,
         performance: performance,
       );
     });
@@ -474,11 +470,11 @@
       performance: performance,
     );
     var file = fileContext.file;
-    var libraryFile = file.kind.library!.file;
+    final libraryKind = file.kind.library ?? file.kind.asLibrary;
 
     // Load the library, link if necessary.
     await libraryContext!.load(
-      targetLibrary: libraryFile,
+      targetLibrary: libraryKind,
       performance: performance,
     );
 
@@ -488,7 +484,7 @@
 
     // Load the library again, the reference count is `>= 2`.
     await libraryContext!.load(
-      targetLibrary: libraryFile,
+      targetLibrary: libraryKind,
       performance: performance,
     );
 
@@ -602,12 +598,12 @@
 
       await performance.runAsync('libraryContext', (performance) async {
         await libraryContext!.load(
-          targetLibrary: libraryFile,
+          targetLibrary: libraryKind,
           performance: performance,
         );
       });
 
-      testView?.addResolvedLibrary(path);
+      testData?.addResolvedLibrary(path);
 
       late Map<FileState, UnitAnalysisResult> results;
 
@@ -617,7 +613,7 @@
           contextObjects!.declaredVariables,
           sourceFactory,
           (_) => true, // _isLibraryUri
-          contextObjects!.analysisContext,
+          libraryContext!.analysisContext,
           libraryContext!.elementFactory,
           contextObjects!.inheritanceManager,
           libraryFile,
@@ -674,7 +670,7 @@
   /// for another.
   void _createContext(String path, AnalysisOptionsImpl fileAnalysisOptions) {
     if (contextObjects != null) {
-      contextObjects!.analysisOptions = fileAnalysisOptions;
+      libraryContext!.analysisContext.analysisOptions = fileAnalysisOptions;
       return;
     }
 
@@ -710,7 +706,8 @@
         ),
         prefetchFiles: prefetchFiles,
         isGenerated: isGenerated,
-      )..testData = testView?.fileSystemTestData;
+        testData: testData?.fileSystem,
+      );
     }
 
     if (contextObjects == null) {
@@ -727,12 +724,21 @@
       );
 
       libraryContext = LibraryContext(
-        testView,
-        logger,
-        resourceProvider,
-        byteStore,
-        contextObjects!,
+        declaredVariables: contextObjects!.declaredVariables,
+        byteStore: byteStore,
+        analysisOptions: contextObjects!.analysisOptions,
+        analysisSession: contextObjects!.analysisSession,
+        logger: logger,
+        fileSystemState: fsState!,
+        sourceFactory: sourceFactory,
+        externalSummaries: SummaryDataStore(),
+        macroExecutor: null,
+        macroKernelBuilder: null,
+        testData: testData?.libraryContext,
       );
+
+      contextObjects!.analysisSession.elementFactory =
+          libraryContext!.elementFactory;
     }
   }
 
@@ -842,14 +848,9 @@
   }
 }
 
-class FileResolverTestView {
-  final FileSystemTestData fileSystemTestData = FileSystemTestData();
-
-  /// Keys: the sorted list of library files.
-  final Map<List<File>, LibraryCycleTestData> libraryCycles = LinkedHashMap(
-    hashCode: Object.hashAll,
-    equals: const ListEquality<File>().equals,
-  );
+class FileResolverTestData {
+  final fileSystem = FileSystemTestData();
+  final libraryContext = LibraryContextTestData();
 
   /// The paths of libraries which were resolved.
   ///
@@ -859,220 +860,6 @@
   void addResolvedLibrary(String path) {
     resolvedLibraries.add(path);
   }
-
-  LibraryCycleTestData forCycle(LibraryCycle cycle) {
-    final files = cycle.libraries.map((e) => e.resource).toList();
-    files.sortBy((file) => file.path);
-
-    return libraryCycles[files] ??= LibraryCycleTestData();
-  }
-}
-
-class LibraryContext {
-  final FileResolverTestView? testData;
-  final PerformanceLog logger;
-  final ResourceProvider resourceProvider;
-  final ByteStore byteStore;
-  final MicroContextObjects contextObjects;
-
-  Set<LibraryCycle> loadedBundles = Set.identity();
-
-  LibraryContext(
-    this.testData,
-    this.logger,
-    this.resourceProvider,
-    this.byteStore,
-    this.contextObjects,
-  );
-
-  LinkedElementFactory get elementFactory {
-    return contextObjects.analysisSession.elementFactory;
-  }
-
-  /// Notifies this object that it is about to be discarded.
-  ///
-  /// Returns the keys of the artifacts that are no longer used.
-  Set<String> dispose() {
-    return unloadAll();
-  }
-
-  /// Load data required to access elements of the given [targetLibrary].
-  Future<void> load({
-    required FileState targetLibrary,
-    required OperationPerformanceImpl performance,
-  }) async {
-    var librariesLinked = 0;
-    var librariesLinkedTimer = Stopwatch();
-    var inputsTimer = Stopwatch();
-
-    Future<void> loadBundle(LibraryCycle cycle) async {
-      if (!loadedBundles.add(cycle)) return;
-
-      performance.getDataInt('cycleCount').increment();
-      performance.getDataInt('libraryCount').add(cycle.libraries.length);
-
-      for (var directDependency in cycle.directDependencies) {
-        await loadBundle(directDependency);
-      }
-
-      var resolutionKey = '${cycle.apiSignature}.linked_bundle';
-      var resolutionBytes = byteStore.get(resolutionKey);
-
-      var unitsInformativeBytes = <Uri, Uint8List>{};
-      for (var library in cycle.libraries) {
-        for (var file in library.libraryFiles) {
-          unitsInformativeBytes[file.uri] = file.unlinked2.informativeBytes;
-        }
-      }
-
-      if (resolutionBytes == null) {
-        librariesLinkedTimer.start();
-
-        inputsTimer.start();
-        var inputLibraries = <LinkInputLibrary>[];
-        for (var libraryFile in cycle.libraries) {
-          var librarySource = libraryFile.source;
-
-          var inputUnits = <LinkInputUnit>[];
-          var partIndex = -1;
-          for (var file in libraryFile.libraryFiles) {
-            var isSynthetic = !file.exists;
-
-            var content = file.getContent();
-            performance.getDataInt('parseCount').increment();
-            performance.getDataInt('parseLength').add(content.length);
-
-            var unit = file.parse2(
-              AnalysisErrorListener.NULL_LISTENER,
-              content,
-            );
-
-            String? partUriStr;
-            if (partIndex >= 0) {
-              partUriStr = libraryFile.unlinked2.parts[partIndex];
-            }
-            partIndex++;
-
-            inputUnits.add(
-              LinkInputUnit(
-                // TODO(scheglov) bad, group part data
-                partDirectiveIndex: partIndex - 1,
-                partUriStr: partUriStr,
-                source: file.source,
-                isSynthetic: isSynthetic,
-                unit: unit,
-              ),
-            );
-          }
-
-          inputLibraries.add(
-            LinkInputLibrary(
-              source: librarySource,
-              units: inputUnits,
-            ),
-          );
-        }
-        inputsTimer.stop();
-
-        var linkResult = await performance.runAsync(
-          'link',
-          (performance) async {
-            return await link2(
-              elementFactory: elementFactory,
-              performance: performance,
-              inputLibraries: inputLibraries,
-            );
-          },
-        );
-
-        librariesLinked += cycle.libraries.length;
-
-        resolutionBytes = linkResult.resolutionBytes;
-        resolutionBytes = byteStore.putGet(resolutionKey, resolutionBytes);
-        performance.getDataInt('bytesPut').add(resolutionBytes.length);
-        testData?.forCycle(cycle).putKeys.add(resolutionKey);
-
-        librariesLinkedTimer.stop();
-      } else {
-        testData?.forCycle(cycle).getKeys.add(resolutionKey);
-        performance.getDataInt('bytesGet').add(resolutionBytes.length);
-        performance.getDataInt('libraryLoadCount').add(cycle.libraries.length);
-        elementFactory.addBundle(
-          BundleReader(
-            elementFactory: elementFactory,
-            unitsInformativeBytes: unitsInformativeBytes,
-            resolutionBytes: resolutionBytes,
-          ),
-        );
-      }
-      cycle.resolutionKey = resolutionKey;
-
-      // We might have just linked dart:core, ensure the type provider.
-      _createElementFactoryTypeProvider();
-    }
-
-    await logger.runAsync('Prepare linked bundles', () async {
-      var libraryCycle = targetLibrary.libraryCycle;
-      await loadBundle(libraryCycle);
-      logger.writeln(
-        '[inputsTimer: ${inputsTimer.elapsedMilliseconds} ms]'
-        '[librariesLinked: $librariesLinked]'
-        '[librariesLinkedTimer: ${librariesLinkedTimer.elapsedMilliseconds} ms]',
-      );
-    });
-  }
-
-  /// Remove libraries represented by the [removed] files.
-  /// If we need these libraries later, we will relink and reattach them.
-  void remove(List<FileState> removed, Set<String> removedKeys) {
-    elementFactory.removeLibraries(
-      removed.map((e) => e.uri).toSet(),
-    );
-
-    var removedSet = removed.toSet();
-
-    loadedBundles.removeWhere((cycle) {
-      if (cycle.libraries.any(removedSet.contains)) {
-        removedKeys.addIfNotNull(cycle.resolutionKey);
-        return true;
-      }
-      return false;
-    });
-  }
-
-  /// Unloads all loaded bundles.
-  ///
-  /// Returns the keys of the artifacts that are no longer used.
-  Set<String> unloadAll() {
-    final keySet = <String>{};
-    final uriSet = <Uri>{};
-
-    for (var cycle in loadedBundles) {
-      keySet.addIfNotNull(cycle.resolutionKey);
-      uriSet.addAll(cycle.libraries.map((e) => e.uri));
-    }
-
-    elementFactory.removeLibraries(uriSet);
-    loadedBundles.clear();
-
-    return keySet;
-  }
-
-  /// Ensure that type provider is created.
-  void _createElementFactoryTypeProvider() {
-    var analysisContext = contextObjects.analysisContext;
-    if (!analysisContext.hasTypeProvider) {
-      elementFactory.createTypeProviders(
-        elementFactory.dartCoreElement,
-        elementFactory.dartAsyncElement,
-      );
-    }
-  }
-}
-
-class LibraryCycleTestData {
-  final List<String> getKeys = [];
-  final List<String> putKeys = [];
 }
 
 class _ContentWithDigest {
diff --git a/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart b/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart
new file mode 100644
index 0000000..7f4b276
--- /dev/null
+++ b/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart
@@ -0,0 +1,211 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/dart/analysis/byte_store.dart';
+import 'package:analyzer/src/dart/analysis/driver.dart';
+import 'package:analyzer/src/dart/analysis/file_state.dart';
+import 'package:analyzer/src/dart/analysis/library_context.dart';
+import 'package:analyzer/src/dart/micro/resolve_file.dart';
+import 'package:collection/collection.dart';
+import 'package:path/path.dart';
+
+class AnalyzerStatePrinter {
+  final MemoryByteStore byteStore;
+  final KeyShorter keyShorter;
+  final LibraryContext libraryContext;
+  final ResourceProvider resourceProvider;
+  final StringSink sink;
+
+  String _indent = '';
+
+  AnalyzerStatePrinter({
+    required this.byteStore,
+    required this.keyShorter,
+    required this.libraryContext,
+    required this.resourceProvider,
+    required this.sink,
+  });
+
+  FileSystemState get fileSystemState => libraryContext.fileSystemState;
+
+  void writeAnalysisDriver(AnalysisDriverTestView testData) {
+    _writeFiles(testData.fileSystem);
+    _writeLibraryContext(testData.libraryContext);
+    _writeElementFactory();
+    _writeByteStore();
+  }
+
+  void writeFileResolver(FileResolverTestData testData) {
+    _writeFiles(testData.fileSystem);
+    _writeLibraryContext(testData.libraryContext);
+    _writeElementFactory();
+    _writeByteStore();
+  }
+
+  /// If the path style is `Windows`, returns the corresponding Posix path.
+  /// Otherwise the path is already a Posix path, and it is returned as is.
+  String _posixPath(File file) {
+    final pathContext = resourceProvider.pathContext;
+    if (pathContext.style == Style.windows) {
+      final components = pathContext.split(file.path);
+      return '/${components.skip(1).join('/')}';
+    } else {
+      return file.path;
+    }
+  }
+
+  void _withIndent(void Function() f) {
+    var indent = _indent;
+    _indent = '$_indent  ';
+    f();
+    _indent = indent;
+  }
+
+  void _writeByteStore() {
+    _writelnWithIndent('byteStore');
+    _withIndent(() {
+      final groups = byteStore.map.entries.groupListsBy((element) {
+        return element.value.refCount;
+      });
+
+      for (final groupEntry in groups.entries) {
+        final keys = groupEntry.value.map((e) => e.key).toList();
+        final shortKeys = keyShorter.shortKeys(keys)..sort();
+        _writelnWithIndent('${groupEntry.key}: $shortKeys');
+      }
+    });
+  }
+
+  void _writeElementFactory() {
+    _writelnWithIndent('elementFactory');
+    _withIndent(() {
+      final elementFactory = libraryContext.elementFactory;
+      _writeUriList(
+        'hasElement',
+        elementFactory.uriListWithLibraryElements,
+      );
+      _writeUriList(
+        'hasReader',
+        elementFactory.uriListWithLibraryReaders,
+      );
+    });
+  }
+
+  void _writeFiles(FileSystemTestData testData) {
+    _writelnWithIndent('files');
+    _withIndent(() {
+      final fileMap = testData.files;
+      final fileDataList = fileMap.values.toList();
+      fileDataList.sortBy((fileData) => fileData.file.path);
+
+      for (final fileData in fileDataList) {
+        final file = fileData.file;
+        _writelnWithIndent(_posixPath(file));
+        _withIndent(() {
+          final current = fileSystemState.getExistingFileForResource(file);
+          if (current != null) {
+            _writelnWithIndent('current');
+            _withIndent(() {
+              final unlinkedShort = keyShorter.shortKey(current.unlinkedKey);
+              _writelnWithIndent('unlinkedKey: $unlinkedShort');
+            });
+          }
+
+          final shortGets = keyShorter.shortKeys(fileData.unlinkedKeyGet);
+          final shortPuts = keyShorter.shortKeys(fileData.unlinkedKeyPut);
+          _writelnWithIndent('unlinkedGet: $shortGets');
+          _writelnWithIndent('unlinkedPut: $shortPuts');
+        });
+      }
+    });
+  }
+
+  void _writeLibraryContext(LibraryContextTestData testData) {
+    _writelnWithIndent('libraryCycles');
+    _withIndent(() {
+      final entries = testData.libraryCycles.entries
+          .mapKey((key) => key.map(_posixPath).join(' '))
+          .toList();
+      entries.sortBy((e) => e.key);
+
+      final loadedBundlesMap = Map.fromEntries(
+        libraryContext.loadedBundles.map((cycle) {
+          final key = cycle.libraries
+              .map((fileState) => fileState.resource)
+              .map(_posixPath)
+              .join(' ');
+          return MapEntry(key, cycle);
+        }),
+      );
+
+      for (final entry in entries) {
+        _writelnWithIndent(entry.key);
+        _withIndent(() {
+          final current = loadedBundlesMap[entry.key];
+          if (current != null) {
+            _writelnWithIndent('current');
+            _withIndent(() {
+              final short = keyShorter.shortKey(current.resolutionKey!);
+              _writelnWithIndent('key: $short');
+            });
+          }
+
+          final shortGets = keyShorter.shortKeys(entry.value.getKeys);
+          final shortPuts = keyShorter.shortKeys(entry.value.putKeys);
+          _writelnWithIndent('get: $shortGets');
+          _writelnWithIndent('put: $shortPuts');
+        });
+      }
+    });
+  }
+
+  void _writelnWithIndent(String line) {
+    sink.write(_indent);
+    sink.writeln(line);
+  }
+
+  void _writeUriList(String name, Iterable<Uri> uriIterable) {
+    final uriStrList = uriIterable.map((uri) => '$uri').toList();
+    if (uriStrList.isNotEmpty) {
+      uriStrList.sort();
+      _writelnWithIndent(name);
+      _withIndent(() {
+        for (final uriStr in uriStrList) {
+          _writelnWithIndent(uriStr);
+        }
+      });
+    }
+  }
+}
+
+/// Keys in the byte store are long hashes, which are hard to read.
+/// So, we generate short unique versions for them.
+class KeyShorter {
+  final Map<String, String> _keyToShort = {};
+  final Map<String, String> _shortToKey = {};
+
+  String shortKey(String key) {
+    var short = _keyToShort[key];
+    if (short == null) {
+      short = 'k${_keyToShort.length.toString().padLeft(2, '0')}';
+      _keyToShort[key] = short;
+      _shortToKey[short] = key;
+    }
+    return short;
+  }
+
+  List<String> shortKeys(List<String> keys) {
+    return keys.map(shortKey).toList();
+  }
+}
+
+extension<K, V> on Iterable<MapEntry<K, V>> {
+  Iterable<MapEntry<K2, V>> mapKey<K2>(K2 Function(K key) convertKey) {
+    return map((e) {
+      final newKey = convertKey(e.key);
+      return MapEntry(newKey, e.value);
+    });
+  }
+}
diff --git a/pkg/analyzer/test/src/dart/analysis/base.dart b/pkg/analyzer/test/src/dart/analysis/base.dart
index 65e6668..7498837 100644
--- a/pkg/analyzer/test/src/dart/analysis/base.dart
+++ b/pkg/analyzer/test/src/dart/analysis/base.dart
@@ -91,6 +91,7 @@
       }),
       enableIndex: true,
       externalSummaries: externalSummaries,
+      testView: AnalysisDriverTestView(),
     );
   }
 
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
index 7bb0a4e..e73428f 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
@@ -25,11 +25,14 @@
 
 @reflectiveTest
 class AnalysisDriverCachingTest extends PubPackageResolutionTest {
+  @override
+  bool get retainDataForTesting => true;
+
   String get testFilePathPlatform => convertPath(testFilePath);
 
   List<Set<String>> get _linkedCycles {
     var driver = driverFor(testFilePath);
-    return driver.test.libraryContextTestView.linkedCycles;
+    return driver.testView!.libraryContext.linkedCycles;
   }
 
   @override
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index ca5fff1..adf42bb 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -748,7 +748,7 @@
     driver.priorityFiles = [a];
 
     ResolvedUnitResult result1 = await driver.getResultValid(a);
-    expect(driver.test.priorityResults, containsPair(a, result1));
+    expect(driver.testView!.priorityResults, containsPair(a, result1));
 
     await waitForIdleWithoutExceptions();
     allResults.clear();
@@ -779,26 +779,26 @@
     driver.priorityFiles = [a];
 
     ResolvedUnitResult result1 = await driver.getResultValid(a);
-    expect(driver.test.priorityResults, containsPair(a, result1));
+    expect(driver.testView!.priorityResults, containsPair(a, result1));
 
     // Change a file.
     // The cache is flushed.
     driver.changeFile(a);
-    expect(driver.test.priorityResults, isEmpty);
+    expect(driver.testView!.priorityResults, isEmpty);
     ResolvedUnitResult result2 = await driver.getResultValid(a);
-    expect(driver.test.priorityResults, containsPair(a, result2));
+    expect(driver.testView!.priorityResults, containsPair(a, result2));
 
     // Add a file.
     // The cache is flushed.
     driver.addFile(b);
-    expect(driver.test.priorityResults, isEmpty);
+    expect(driver.testView!.priorityResults, isEmpty);
     ResolvedUnitResult result3 = await driver.getResultValid(a);
-    expect(driver.test.priorityResults, containsPair(a, result3));
+    expect(driver.testView!.priorityResults, containsPair(a, result3));
 
     // Remove a file.
     // The cache is flushed.
     driver.removeFile(b);
-    expect(driver.test.priorityResults, isEmpty);
+    expect(driver.testView!.priorityResults, isEmpty);
   }
 
   test_cachedPriorityResults_flush_onPrioritySetChange() async {
@@ -810,26 +810,26 @@
     driver.priorityFiles = [a];
 
     ResolvedUnitResult result1 = await driver.getResultValid(a);
-    expect(driver.test.priorityResults, hasLength(1));
-    expect(driver.test.priorityResults, containsPair(a, result1));
+    expect(driver.testView!.priorityResults, hasLength(1));
+    expect(driver.testView!.priorityResults, containsPair(a, result1));
 
     // Make "a" and "b" priority.
     // We still have the result for "a" cached.
     driver.priorityFiles = [a, b];
-    expect(driver.test.priorityResults, hasLength(1));
-    expect(driver.test.priorityResults, containsPair(a, result1));
+    expect(driver.testView!.priorityResults, hasLength(1));
+    expect(driver.testView!.priorityResults, containsPair(a, result1));
 
     // Get the result for "b".
     ResolvedUnitResult result2 = await driver.getResultValid(b);
-    expect(driver.test.priorityResults, hasLength(2));
-    expect(driver.test.priorityResults, containsPair(a, result1));
-    expect(driver.test.priorityResults, containsPair(b, result2));
+    expect(driver.testView!.priorityResults, hasLength(2));
+    expect(driver.testView!.priorityResults, containsPair(a, result1));
+    expect(driver.testView!.priorityResults, containsPair(b, result2));
 
     // Only "b" is priority.
     // The result for "a" is flushed.
     driver.priorityFiles = [b];
-    expect(driver.test.priorityResults, hasLength(1));
-    expect(driver.test.priorityResults, containsPair(b, result2));
+    expect(driver.testView!.priorityResults, hasLength(1));
+    expect(driver.testView!.priorityResults, containsPair(b, result2));
   }
 
   test_cachedPriorityResults_notPriority() async {
@@ -837,7 +837,7 @@
     newFile(a, 'var a = 1;');
 
     ResolvedUnitResult result1 = await driver.getResultValid(a);
-    expect(driver.test.priorityResults, isEmpty);
+    expect(driver.testView!.priorityResults, isEmpty);
 
     // The file is not priority, so its result is not cached.
     ResolvedUnitResult result2 = await driver.getResultValid(a);
@@ -872,7 +872,7 @@
     driver.changeFile(b);
 
     // "b" is not an added file, so it is not scheduled for analysis.
-    expect(driver.test.fileTracker.hasPendingFiles, isFalse);
+    expect(driver.testView!.fileTracker.hasPendingFiles, isFalse);
 
     // While "b" is not analyzed explicitly, it is analyzed implicitly.
     // The change causes "a" to be reanalyzed.
@@ -3585,7 +3585,7 @@
     Iterable<String>? included,
     Iterable<String>? excluded,
   }) {
-    var uriSet = this.test.loadedLibraryUriSet;
+    var uriSet = testView!.loadedLibraryUriSet;
     if (included != null) {
       expect(uriSet, containsAll(included));
     }
diff --git a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
index 403a89e..3cf0ed1 100644
--- a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
@@ -122,6 +122,9 @@
 
 @reflectiveTest
 class FileSystemState_PubPackageTest extends PubPackageResolutionTest {
+  @override
+  bool get retainDataForTesting => true;
+
   FileState get _dartAsyncState {
     return fileStateForUriStr('dart:async');
   }
@@ -2245,6 +2248,7 @@
       ),
       prefetchFiles: null,
       isGenerated: (_) => false,
+      testData: null,
     );
   }
 
diff --git a/pkg/analyzer/test/src/dart/micro/file_resolution.dart b/pkg/analyzer/test/src/dart/micro/file_resolution.dart
index db6d5af..dd4736b 100644
--- a/pkg/analyzer/test/src/dart/micro/file_resolution.dart
+++ b/pkg/analyzer/test/src/dart/micro/file_resolution.dart
@@ -8,6 +8,7 @@
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/dart/analysis/byte_store.dart';
 import 'package:analyzer/src/dart/analysis/file_state.dart';
+import 'package:analyzer/src/dart/analysis/library_context.dart';
 import 'package:analyzer/src/dart/analysis/performance_logger.dart';
 import 'package:analyzer/src/dart/micro/resolve_file.dart';
 import 'package:analyzer/src/dart/sdk/sdk.dart';
@@ -17,12 +18,11 @@
 import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:analyzer/src/util/performance/operation_performance.dart';
 import 'package:analyzer/src/workspace/bazel.dart';
-import 'package:collection/collection.dart';
 import 'package:crypto/crypto.dart';
 import 'package:linter/src/rules.dart';
-import 'package:path/path.dart';
 import 'package:test/test.dart';
 
+import '../analysis/analyzer_state_printer.dart' as printer;
 import '../resolution/resolution.dart';
 
 /// [FileResolver] based implementation of [ResolutionTest].
@@ -31,14 +31,14 @@
 
   final MemoryByteStore byteStore = MemoryByteStore();
 
-  final FileResolverTestView testData = FileResolverTestView();
+  final FileResolverTestData testData = FileResolverTestData();
 
   final StringBuffer logBuffer = StringBuffer();
   late PerformanceLog logger;
 
   late FileResolver fileResolver;
 
-  final _KeyShorter _keyShorter = _KeyShorter();
+  final printer.KeyShorter _keyShorter = printer.KeyShorter();
 
   FileSystemState get fsState => fileResolver.fsState!;
 
@@ -66,8 +66,13 @@
 
   void assertStateString(String expected) {
     final buffer = StringBuffer();
-    ResolverStatePrinter(resourceProvider, buffer, _keyShorter)
-        .write(byteStore, fileResolver.fsState!, libraryContext, testData);
+    printer.AnalyzerStatePrinter(
+      byteStore: byteStore,
+      keyShorter: _keyShorter,
+      libraryContext: libraryContext,
+      resourceProvider: resourceProvider,
+      sink: buffer,
+    ).writeFileResolver(testData);
     final actual = buffer.toString();
 
     if (actual != expected) {
@@ -97,8 +102,8 @@
       workspace: workspace,
       prefetchFiles: null,
       isGenerated: (_) => false,
+      testData: testData,
     );
-    fileResolver.testView = testData;
   }
 
   Future<ErrorsResult> getTestErrors() async {
@@ -154,176 +159,3 @@
     }
   }
 }
-
-class ResolverStatePrinter {
-  final ResourceProvider _resourceProvider;
-
-  /// The target sink to print.
-  final StringSink _sink;
-
-  final _KeyShorter _keyShorter;
-
-  String _indent = '';
-
-  ResolverStatePrinter(this._resourceProvider, this._sink, this._keyShorter);
-
-  void write(MemoryByteStore byteStore, FileSystemState fileSystemState,
-      LibraryContext libraryContext, FileResolverTestView testData) {
-    _writelnWithIndent('files');
-    _withIndent(() {
-      final fileMap = testData.fileSystemTestData.files;
-      final fileDataList = fileMap.values.toList();
-      fileDataList.sortBy((fileData) => fileData.file.path);
-
-      for (final fileData in fileDataList) {
-        final file = fileData.file;
-        _writelnWithIndent(_posixPath(file));
-        _withIndent(() {
-          final current = fileSystemState.getExistingFileForResource(file);
-          if (current != null) {
-            _writelnWithIndent('current');
-            _withIndent(() {
-              final unlinkedShort = _keyShorter.shortKey(current.unlinkedKey);
-              _writelnWithIndent('unlinkedKey: $unlinkedShort');
-            });
-          }
-
-          final shortGets = _keyShorter.shortKeys(fileData.unlinkedKeyGet);
-          final shortPuts = _keyShorter.shortKeys(fileData.unlinkedKeyPut);
-          _writelnWithIndent('unlinkedGet: $shortGets');
-          _writelnWithIndent('unlinkedPut: $shortPuts');
-        });
-      }
-    });
-
-    _writelnWithIndent('libraryCycles');
-    _withIndent(() {
-      final entries = testData.libraryCycles.entries
-          .mapKey((key) => key.map(_posixPath).join(' '))
-          .toList();
-      entries.sortBy((e) => e.key);
-
-      final loadedBundlesMap = Map.fromEntries(
-        libraryContext.loadedBundles.map((cycle) {
-          final key = cycle.libraries
-              .map((fileState) => fileState.resource)
-              .map(_posixPath)
-              .join(' ');
-          return MapEntry(key, cycle);
-        }),
-      );
-
-      for (final entry in entries) {
-        _writelnWithIndent(entry.key);
-        _withIndent(() {
-          final current = loadedBundlesMap[entry.key];
-          if (current != null) {
-            _writelnWithIndent('current');
-            _withIndent(() {
-              final short = _keyShorter.shortKey(current.resolutionKey!);
-              _writelnWithIndent('key: $short');
-            });
-          }
-
-          final shortGets = _keyShorter.shortKeys(entry.value.getKeys);
-          final shortPuts = _keyShorter.shortKeys(entry.value.putKeys);
-          _writelnWithIndent('get: $shortGets');
-          _writelnWithIndent('put: $shortPuts');
-        });
-      }
-    });
-
-    _writelnWithIndent('elementFactory');
-    _withIndent(() {
-      final elementFactory = libraryContext.elementFactory;
-      _writeUriList(
-        'hasElement',
-        elementFactory.uriListWithLibraryElements,
-      );
-      _writeUriList(
-        'hasReader',
-        elementFactory.uriListWithLibraryReaders,
-      );
-    });
-
-    _writelnWithIndent('byteStore');
-    _withIndent(() {
-      final groups = byteStore.map.entries.groupListsBy((element) {
-        return element.value.refCount;
-      });
-
-      for (final groupEntry in groups.entries) {
-        final keys = groupEntry.value.map((e) => e.key).toList();
-        final shortKeys = _keyShorter.shortKeys(keys)..sort();
-        _writelnWithIndent('${groupEntry.key}: $shortKeys');
-      }
-    });
-  }
-
-  /// If the path style is `Windows`, returns the corresponding Posix path.
-  /// Otherwise the path is already a Posix path, and it is returned as is.
-  String _posixPath(File file) {
-    final pathContext = _resourceProvider.pathContext;
-    if (pathContext.style == Style.windows) {
-      final components = pathContext.split(file.path);
-      return '/${components.skip(1).join('/')}';
-    } else {
-      return file.path;
-    }
-  }
-
-  void _withIndent(void Function() f) {
-    var indent = _indent;
-    _indent = '$_indent  ';
-    f();
-    _indent = indent;
-  }
-
-  void _writelnWithIndent(String line) {
-    _sink.write(_indent);
-    _sink.writeln(line);
-  }
-
-  void _writeUriList(String name, Iterable<Uri> uriIterable) {
-    final uriStrList = uriIterable.map((uri) => '$uri').toList();
-    if (uriStrList.isNotEmpty) {
-      uriStrList.sort();
-      _writelnWithIndent(name);
-      _withIndent(() {
-        for (final uriStr in uriStrList) {
-          _writelnWithIndent(uriStr);
-        }
-      });
-    }
-  }
-}
-
-/// Keys in the byte store are long hashes, which are hard to read.
-/// So, we generate short unique versions for them.
-class _KeyShorter {
-  final Map<String, String> _keyToShort = {};
-  final Map<String, String> _shortToKey = {};
-
-  String shortKey(String key) {
-    var short = _keyToShort[key];
-    if (short == null) {
-      short = 'k${_keyToShort.length.toString().padLeft(2, '0')}';
-      _keyToShort[key] = short;
-      _shortToKey[short] = key;
-    }
-    return short;
-  }
-
-  List<String> shortKeys(List<String> keys) {
-    return keys.map(shortKey).toList();
-  }
-}
-
-extension<K, V> on Iterable<MapEntry<K, V>> {
-  Iterable<MapEntry<K2, V>> mapKey<K2>(K2 Function(K key) convertKey) {
-    return map((e) {
-      final newKey = convertKey(e.key);
-      return MapEntry(newKey, e.value);
-    });
-  }
-}
diff --git a/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart b/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
index be50344..3bf1dce 100644
--- a/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
+++ b/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
@@ -2716,7 +2716,7 @@
     List<File> expected, {
     bool andClear = true,
   }) {
-    final actual = fileResolver.testView!.resolvedLibraries;
+    final actual = fileResolver.testData!.resolvedLibraries;
     expect(actual, expected.map((e) => e.path).toList());
     if (andClear) {
       actual.clear();
diff --git a/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart b/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart
index 6c41892..a8d81f3 100644
--- a/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart
+++ b/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart
@@ -30,6 +30,7 @@
 
 import '../../../generated/test_support.dart';
 import '../../summary/macros_environment.dart';
+import '../analysis/analyzer_state_printer.dart';
 import 'context_collection_resolution_caching.dart';
 import 'resolution.dart';
 
@@ -122,7 +123,7 @@
     with ResourceProviderMixin, ResolutionTest {
   static bool _lintRulesAreRegistered = false;
 
-  ByteStore _byteStore = getContextResolutionTestByteStore();
+  MemoryByteStore _byteStore = getContextResolutionTestByteStore();
 
   Map<String, String> _declaredVariables = {};
   AnalysisContextCollectionImpl? _analysisContextCollection;
@@ -137,6 +138,8 @@
   /// Optional summaries to provide for the collection.
   List<File>? librarySummaryFiles;
 
+  final KeyShorter _keyShorter = KeyShorter();
+
   List<MockSdkLibrary> get additionalMockSdkLibraries => [];
 
   List<String> get collectionIncludedPaths;
@@ -163,6 +166,25 @@
     expect(workspace, TypeMatcher<BazelWorkspace>());
   }
 
+  void assertDriverStateString(File file, String expected) {
+    final analysisDriver = driverFor(file.path);
+
+    final buffer = StringBuffer();
+    AnalyzerStatePrinter(
+      byteStore: _byteStore,
+      keyShorter: _keyShorter,
+      libraryContext: analysisDriver.libraryContext,
+      resourceProvider: resourceProvider,
+      sink: buffer,
+    ).writeAnalysisDriver(analysisDriver.testView!);
+    final actual = buffer.toString();
+
+    if (actual != expected) {
+      print(actual);
+    }
+    expect(actual, expected);
+  }
+
   void assertGnWorkspaceFor(String path) {
     var workspace = contextFor(path).contextRoot.workspace;
     expect(workspace, TypeMatcher<GnWorkspace>());
diff --git a/pkg/analyzer/test/src/dart/resolution/context_collection_resolution_caching.dart b/pkg/analyzer/test/src/dart/resolution/context_collection_resolution_caching.dart
index 9f2dc62..9bef306 100644
--- a/pkg/analyzer/test/src/dart/resolution/context_collection_resolution_caching.dart
+++ b/pkg/analyzer/test/src/dart/resolution/context_collection_resolution_caching.dart
@@ -7,7 +7,7 @@
 final _sharedByteStore = MemoryByteStore();
 final _useSharedByteStore = false;
 
-ByteStore getContextResolutionTestByteStore() {
+MemoryByteStore getContextResolutionTestByteStore() {
   if (_useSharedByteStore) {
     return _sharedByteStore;
   } else {
diff --git a/pkg/analyzer_plugin/CHANGELOG.md b/pkg/analyzer_plugin/CHANGELOG.md
index d691820..99be7f5 100644
--- a/pkg/analyzer_plugin/CHANGELOG.md
+++ b/pkg/analyzer_plugin/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 0.11.0
+- Using `AnalysisContextCollection` and `AnalysisContext` for analysis.
+
 ## 0.10.0
 - Support version `4.x` of the `analyzer` package
 
diff --git a/pkg/analyzer_plugin/lib/plugin/plugin.dart b/pkg/analyzer_plugin/lib/plugin/plugin.dart
index f56f362..fe76e56 100644
--- a/pkg/analyzer_plugin/lib/plugin/plugin.dart
+++ b/pkg/analyzer_plugin/lib/plugin/plugin.dart
@@ -2,23 +2,20 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:analyzer/dart/analysis/analysis_context.dart';
+import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/file_system/overlay_file_system.dart';
-import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
 import 'package:analyzer/src/dart/analysis/byte_store.dart';
-import 'package:analyzer/src/dart/analysis/driver.dart'
-    show AnalysisDriver, AnalysisDriverGeneric, AnalysisDriverScheduler;
-import 'package:analyzer/src/dart/analysis/file_byte_store.dart';
-import 'package:analyzer/src/dart/analysis/performance_logger.dart';
-import 'package:analyzer/src/generated/sdk.dart';
+import 'package:analyzer/src/dart/analysis/file_content_cache.dart';
 import 'package:analyzer_plugin/channel/channel.dart';
 import 'package:analyzer_plugin/protocol/protocol.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:analyzer_plugin/protocol/protocol_constants.dart';
 import 'package:analyzer_plugin/protocol/protocol_generated.dart';
 import 'package:analyzer_plugin/src/protocol/protocol_internal.dart';
-import 'package:analyzer_plugin/src/utilities/null_string_sink.dart';
 import 'package:analyzer_plugin/utilities/subscriptions/subscription_manager.dart';
 import 'package:pub_semver/pub_semver.dart';
 
@@ -28,9 +25,6 @@
 /// Clients may not implement or mix-in this class, but are expected to extend
 /// it.
 abstract class ServerPlugin {
-  /// A megabyte.
-  static const int M = 1024 * 1024;
-
   /// The communication channel being used to communicate with the analysis
   /// server.
   late PluginCommunicationChannel _channel;
@@ -38,48 +32,31 @@
   /// The resource provider used to access the file system.
   final OverlayResourceProvider resourceProvider;
 
+  late final ByteStore _byteStore = createByteStore();
+
+  AnalysisContextCollectionImpl? _contextCollection;
+
   /// The next modification stamp for a changed file in the [resourceProvider].
   int _overlayModificationStamp = 0;
 
+  /// The path to the Dart SDK, set by the analysis server.
+  String? _sdkPath;
+
+  /// Paths of priority files.
+  Set<String> priorityPaths = {};
+
   /// The object used to manage analysis subscriptions.
   final SubscriptionManager subscriptionManager = SubscriptionManager();
 
-  /// The scheduler used by any analysis drivers that are created.
-  late AnalysisDriverScheduler analysisDriverScheduler;
-
-  /// A table mapping the current context roots to the analysis driver created
-  /// for that root.
-  final Map<ContextRoot, AnalysisDriverGeneric> driverMap =
-      <ContextRoot, AnalysisDriverGeneric>{};
-
-  /// The performance log used by any analysis drivers that are created.
-  final PerformanceLog performanceLog = PerformanceLog(NullStringSink());
-
-  /// The byte store used by any analysis drivers that are created, or `null` if
-  /// the cache location isn't known because the 'plugin.version' request has not
-  /// yet been received.
-  late ByteStore _byteStore;
-
-  /// The SDK manager used to manage SDKs.
-  late DartSdkManager _sdkManager;
-
-  /// Initialize a newly created analysis server plugin. If a resource [provider]
+  /// Initialize a newly created analysis server plugin. If a resource [resourceProvider]
   /// is given, then it will be used to access the file system. Otherwise a
   /// resource provider that accesses the physical file system will be used.
-  ServerPlugin(ResourceProvider? provider)
-      : resourceProvider = OverlayResourceProvider(
-            provider ?? PhysicalResourceProvider.INSTANCE) {
-    analysisDriverScheduler = AnalysisDriverScheduler(performanceLog);
-    analysisDriverScheduler.start();
-  }
-
-  /// Return the byte store used by any analysis drivers that are created, or
-  /// `null` if the cache location isn't known because the 'plugin.version'
-  /// request has not yet been received.
-  ByteStore get byteStore => _byteStore;
+  ServerPlugin({
+    required ResourceProvider resourceProvider,
+  }) : resourceProvider = OverlayResourceProvider(resourceProvider);
 
   /// Return the communication channel being used to communicate with the
-  /// analysis server, or `null` if the plugin has not been started.
+  /// analysis server.
   PluginCommunicationChannel get channel => _channel;
 
   /// Return the user visible information about how to contact the plugin authors
@@ -93,88 +70,150 @@
   /// Return the user visible name of this plugin.
   String get name;
 
-  /// Return the SDK manager used to manage SDKs.
-  DartSdkManager get sdkManager => _sdkManager;
-
   /// Return the version number of the plugin spec required by this plugin,
   /// encoded as a string.
   String get version;
 
-  /// Handle the fact that the file with the given [path] has been modified.
-  void contentChanged(String path) {
-    // Ignore changes to files.
+  /// This method is invoked when a new instance of [AnalysisContextCollection]
+  /// is created, so the plugin can perform initial analysis of analyzed files.
+  ///
+  /// By default analyzes every [AnalysisContext] with [analyzeFiles].
+  Future<void> afterNewContextCollection({
+    required AnalysisContextCollection contextCollection,
+  }) async {
+    await _forAnalysisContexts(contextCollection, (analysisContext) async {
+      final paths = analysisContext.contextRoot.analyzedFiles().toList();
+      await analyzeFiles(
+        analysisContext: analysisContext,
+        paths: paths,
+      );
+    });
   }
 
-  /// Return the context root containing the file at the given [filePath].
-  ContextRoot? contextRootContaining(String filePath) {
-    var pathContext = resourceProvider.pathContext;
+  /// Analyzes the given file.
+  Future<void> analyzeFile({
+    required AnalysisContext analysisContext,
+    required String path,
+  });
 
-    /// Return `true` if the given [child] is either the same as or within the
-    /// given [parent].
-    bool isOrWithin(String parent, String child) {
-      return parent == child || pathContext.isWithin(parent, child);
+  /// Analyzes the given files.
+  /// By default invokes [analyzeFile] for every file.
+  /// Implementations may override to optimize for batch analysis.
+  Future<void> analyzeFiles({
+    required AnalysisContext analysisContext,
+    required List<String> paths,
+  }) async {
+    final pathSet = paths.toSet();
+
+    // First analyze priority files.
+    for (final path in priorityPaths) {
+      pathSet.remove(path);
+      await analyzeFile(
+        analysisContext: analysisContext,
+        path: path,
+      );
     }
 
-    /// Return `true` if the given context [root] contains the target [file].
-    bool ownsFile(ContextRoot root) {
-      if (isOrWithin(root.root, filePath)) {
-        var excludedPaths = root.exclude;
-        for (var excludedPath in excludedPaths) {
-          if (isOrWithin(excludedPath, filePath)) {
-            return false;
-          }
+    // Then analyze the remaining files.
+    for (final path in pathSet) {
+      await analyzeFile(
+        analysisContext: analysisContext,
+        path: path,
+      );
+    }
+  }
+
+  /// This method is invoked immediately before the current
+  /// [AnalysisContextCollection] is disposed.
+  Future<void> beforeContextCollectionDispose({
+    required AnalysisContextCollection contextCollection,
+  }) async {}
+
+  /// Handle the fact that files with [paths] were changed.
+  Future<void> contentChanged(List<String> paths) async {
+    final contextCollection = _contextCollection;
+    if (contextCollection != null) {
+      await _forAnalysisContexts(contextCollection, (analysisContext) async {
+        for (final path in paths) {
+          analysisContext.changeFile(path);
         }
-        return true;
-      }
-      return false;
+        final affected = await analysisContext.applyPendingFileChanges();
+        await handleAffectedFiles(
+          analysisContext: analysisContext,
+          paths: affected,
+        );
+      });
     }
-
-    for (var root in driverMap.keys) {
-      if (ownsFile(root)) {
-        return root;
-      }
-    }
-    return null;
   }
 
-  /// Create an analysis driver that can analyze the files within the given
-  /// [contextRoot].
-  AnalysisDriverGeneric createAnalysisDriver(ContextRoot contextRoot);
+  /// This method is invoked once to create the [ByteStore] that is used for
+  /// all [AnalysisContextCollection] instances, and reused when new instances
+  /// are created (and so can perform analysis faster).
+  ByteStore createByteStore() {
+    return MemoryCachingByteStore(
+      NullByteStore(),
+      1024 * 1024 * 256,
+    );
+  }
 
-  /// Return the driver being used to analyze the file with the given [path].
-  AnalysisDriverGeneric? driverForPath(String path) {
-    var contextRoot = contextRootContaining(path);
-    if (contextRoot == null) {
-      return null;
+  /// Plugin implementations can use this method to flush the state of
+  /// analysis, so reduce the used heap size, after performing a set of
+  /// operations, e.g. in [afterNewContextCollection] or [handleAffectedFiles].
+  ///
+  /// The next analysis operation will be slower, because it will restore
+  /// the state from the byte store cache, or recompute.
+  Future<void> flushAnalysisState({
+    bool elementModels = true,
+  }) async {
+    final contextCollection = _contextCollection;
+    if (contextCollection != null) {
+      for (final analysisContext in contextCollection.contexts) {
+        if (elementModels) {
+          analysisContext.driver.clearLibraryContext();
+        }
+      }
     }
-    return driverMap[contextRoot];
   }
 
   /// Return the result of analyzing the file with the given [path].
   ///
-  /// Throw a [RequestFailure] is the file cannot be analyzed or if the driver
-  /// associated with the file is not an [AnalysisDriver].
+  /// Throw a [RequestFailure] is the file cannot be analyzed.
   Future<ResolvedUnitResult> getResolvedUnitResult(String path) async {
-    var driver = driverForPath(path);
-    if (driver is! AnalysisDriver) {
-      // Return an error from the request.
-      throw RequestFailure(
-          RequestErrorFactory.pluginError('Failed to analyze $path', null));
+    final contextCollection = _contextCollection;
+    if (contextCollection != null) {
+      final analysisContext = contextCollection.contextFor(path);
+      final analysisSession = analysisContext.currentSession;
+      final unitResult = await analysisSession.getResolvedUnit(path);
+      if (unitResult is ResolvedUnitResult) {
+        return unitResult;
+      }
     }
-    var result = await driver.getResult(path);
-    if (result is! ResolvedUnitResult) {
-      // Return an error from the request.
-      throw RequestFailure(
-          RequestErrorFactory.pluginError('Failed to analyze $path', null));
-    }
-    return result;
+    // Return an error from the request.
+    throw RequestFailure(
+      RequestErrorFactory.pluginError('Failed to analyze $path', null),
+    );
+  }
+
+  /// Handles files that might have been affected by a content change of
+  /// one or more files. The implementation may check if these files should
+  /// be analyzed, do such analysis, and send diagnostics.
+  ///
+  /// By default invokes [analyzeFiles].
+  Future<void> handleAffectedFiles({
+    required AnalysisContext analysisContext,
+    required List<String> paths,
+  }) async {
+    await analyzeFiles(
+      analysisContext: analysisContext,
+      paths: paths,
+    );
   }
 
   /// Handle an 'analysis.getNavigation' request.
   ///
   /// Throw a [RequestFailure] if the request could not be handled.
   Future<AnalysisGetNavigationResult> handleAnalysisGetNavigation(
-      AnalysisGetNavigationParams params) async {
+      AnalysisGetNavigationParams parameters) async {
     return AnalysisGetNavigationResult(
         <String>[], <NavigationTarget>[], <NavigationRegion>[]);
   }
@@ -190,7 +229,7 @@
           // TODO(brianwilkerson) Handle the event.
           break;
         case WatchEventType.MODIFY:
-          contentChanged(event.path);
+          await contentChanged([event.path]);
           break;
         case WatchEventType.REMOVE:
           // TODO(brianwilkerson) Handle the event.
@@ -208,27 +247,27 @@
   /// Throw a [RequestFailure] if the request could not be handled.
   Future<AnalysisSetContextRootsResult> handleAnalysisSetContextRoots(
       AnalysisSetContextRootsParams parameters) async {
-    var contextRoots = parameters.roots;
-    var oldRoots = driverMap.keys.toList();
-    for (var contextRoot in contextRoots) {
-      if (!oldRoots.remove(contextRoot)) {
-        // The context is new, so we create a driver for it. Creating the driver
-        // has the side-effect of adding it to the analysis driver scheduler.
-        var driver = createAnalysisDriver(contextRoot);
-        driverMap[contextRoot] = driver;
-        _addFilesToDriver(
-            driver,
-            resourceProvider.getResource(contextRoot.root),
-            contextRoot.exclude);
-      }
+    final currentContextCollection = _contextCollection;
+    if (currentContextCollection != null) {
+      _contextCollection = null;
+      await beforeContextCollectionDispose(
+        contextCollection: currentContextCollection,
+      );
+      currentContextCollection.dispose();
     }
-    for (var contextRoot in oldRoots) {
-      // The context has been removed, so we remove its driver.
-      var driver = driverMap.remove(contextRoot);
-      // The `dispose` method has the side-effect of removing the driver from
-      // the analysis driver scheduler.
-      driver?.dispose();
-    }
+
+    final includedPaths = parameters.roots.map((e) => e.root).toList();
+    final contextCollection = AnalysisContextCollectionImpl(
+      resourceProvider: resourceProvider,
+      includedPaths: includedPaths,
+      byteStore: _byteStore,
+      sdkPath: _sdkPath,
+      fileContentCache: FileContentCache(resourceProvider),
+    );
+    _contextCollection = contextCollection;
+    await afterNewContextCollection(
+      contextCollection: contextCollection,
+    );
     return AnalysisSetContextRootsResult();
   }
 
@@ -237,19 +276,7 @@
   /// Throw a [RequestFailure] if the request could not be handled.
   Future<AnalysisSetPriorityFilesResult> handleAnalysisSetPriorityFiles(
       AnalysisSetPriorityFilesParams parameters) async {
-    var files = parameters.files;
-    var filesByDriver = <AnalysisDriverGeneric, List<String>>{};
-    for (var file in files) {
-      var contextRoot = contextRootContaining(file);
-      if (contextRoot != null) {
-        // TODO(brianwilkerson) Which driver should we use if there is no context root?
-        var driver = driverMap[contextRoot]!;
-        filesByDriver.putIfAbsent(driver, () => <String>[]).add(file);
-      }
-    }
-    filesByDriver.forEach((AnalysisDriverGeneric driver, List<String> files) {
-      driver.priorityFiles = files;
-    });
+    priorityPaths = parameters.files.toSet();
     return AnalysisSetPriorityFilesResult();
   }
 
@@ -273,13 +300,14 @@
   /// Throw a [RequestFailure] if the request could not be handled.
   Future<AnalysisUpdateContentResult> handleAnalysisUpdateContent(
       AnalysisUpdateContentParams parameters) async {
-    var files = parameters.files;
-    files.forEach((String filePath, Object? overlay) {
+    final changedPaths = <String>{};
+    var paths = parameters.files;
+    paths.forEach((String path, Object? overlay) {
       // Prepare the old overlay contents.
       String? oldContents;
       try {
-        if (resourceProvider.hasOverlay(filePath)) {
-          var file = resourceProvider.getFile(filePath);
+        if (resourceProvider.hasOverlay(path)) {
+          var file = resourceProvider.getFile(path);
           oldContents = file.readAsStringSync();
         }
       } catch (_) {}
@@ -307,16 +335,17 @@
 
       if (newContents != null) {
         resourceProvider.setOverlay(
-          filePath,
+          path,
           content: newContents,
           modificationStamp: _overlayModificationStamp++,
         );
       } else {
-        resourceProvider.removeOverlay(filePath);
+        resourceProvider.removeOverlay(path);
       }
 
-      contentChanged(filePath);
+      changedPaths.add(path);
     });
+    await contentChanged(changedPaths.toList());
     return AnalysisUpdateContentResult();
   }
 
@@ -386,15 +415,9 @@
   /// Throw a [RequestFailure] if the request could not be handled.
   Future<PluginVersionCheckResult> handlePluginVersionCheck(
       PluginVersionCheckParams parameters) async {
-    var byteStorePath = parameters.byteStorePath;
-    var sdkPath = parameters.sdkPath;
+    _sdkPath = parameters.sdkPath;
     var versionString = parameters.version;
     var serverVersion = Version.parse(versionString);
-    _byteStore = MemoryCachingByteStore(
-        FileByteStore(byteStorePath,
-            tempNameSuffix: DateTime.now().millisecondsSinceEpoch.toString()),
-        64 * M);
-    _sdkManager = DartSdkManager(sdkPath);
     return PluginVersionCheckResult(
         isCompatibleWith(serverVersion), name, version, fileGlobsToAnalyze,
         contactInfo: contactInfo);
@@ -479,25 +502,23 @@
     _channel.listen(_onRequest, onError: onError, onDone: onDone);
   }
 
-  /// Add all of the files contained in the given [resource] that are not in the
-  /// list of [excluded] resources to the given [driver].
-  void _addFilesToDriver(
-      AnalysisDriverGeneric driver, Resource resource, List<String> excluded) {
-    var path = resource.path;
-    if (excluded.contains(path)) {
-      return;
-    }
-    if (resource is File) {
-      driver.addFile(path);
-    } else if (resource is Folder) {
-      try {
-        for (var child in resource.getChildren()) {
-          _addFilesToDriver(driver, child, excluded);
-        }
-      } on FileSystemException {
-        // The folder does not exist, so ignore it.
+  /// Invokes [f] first for priority analysis contexts, then for the rest.
+  Future<void> _forAnalysisContexts(
+    AnalysisContextCollection contextCollection,
+    Future<void> Function(AnalysisContext analysisContext) f,
+  ) async {
+    final nonPriorityAnalysisContexts = <AnalysisContext>[];
+    for (final analysisContext in contextCollection.contexts) {
+      if (_isPriorityAnalysisContext(analysisContext)) {
+        await f(analysisContext);
+      } else {
+        nonPriorityAnalysisContexts.add(analysisContext);
       }
     }
+
+    for (final analysisContext in nonPriorityAnalysisContexts) {
+      await f(analysisContext);
+    }
   }
 
   /// Compute the response that should be returned for the given [request], or
@@ -571,6 +592,10 @@
     return result.toResponse(request.id, requestTime);
   }
 
+  bool _isPriorityAnalysisContext(AnalysisContext analysisContext) {
+    return priorityPaths.any(analysisContext.contextRoot.isAnalyzed);
+  }
+
   /// The method that is called when a [request] is received from the analysis
   /// server.
   Future<void> _onRequest(Request request) async {
diff --git a/pkg/analyzer_plugin/pubspec.yaml b/pkg/analyzer_plugin/pubspec.yaml
index 0115b6f..c3322ed 100644
--- a/pkg/analyzer_plugin/pubspec.yaml
+++ b/pkg/analyzer_plugin/pubspec.yaml
@@ -1,13 +1,13 @@
 name: analyzer_plugin
 description: A framework and support code for building plugins for the analysis server.
-version: 0.10.0
+version: 0.11.0
 repository: https://github.com/dart-lang/sdk/tree/main/pkg/analyzer_plugin
 
 environment:
-  sdk: '>=2.14.0 <3.0.0'
+  sdk: '>=2.17.0 <3.0.0'
 
 dependencies:
-  analyzer: ^4.0.0
+  analyzer: ^4.1.0
   collection: ^1.15.0
   dart_style: ^2.2.1
   pub_semver: ^2.0.0
@@ -20,6 +20,7 @@
 dev_dependencies:
   analyzer_utilities: any
   html: any
+  meta: any
   path: any
   test_reflective_loader: any
   test: any
diff --git a/pkg/analyzer_plugin/test/plugin/assist_mixin_test.dart b/pkg/analyzer_plugin/test/plugin/assist_mixin_test.dart
index 475a6552..4c1e64c 100644
--- a/pkg/analyzer_plugin/test/plugin/assist_mixin_test.dart
+++ b/pkg/analyzer_plugin/test/plugin/assist_mixin_test.dart
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:analyzer_plugin/plugin/assist_mixin.dart';
+import 'package:analyzer_plugin/plugin/plugin.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:analyzer_plugin/protocol/protocol_generated.dart';
 import 'package:analyzer_plugin/src/utilities/assist/assist.dart';
@@ -13,29 +13,31 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'mocks.dart';
+import 'plugin_test.dart';
 
 void main() {
   defineReflectiveTests(AssistsMixinTest);
 }
 
 @reflectiveTest
-class AssistsMixinTest with ResourceProviderMixin {
+class AssistsMixinTest extends AbstractPluginTest {
   late String packagePath1;
   late String filePath1;
   late ContextRoot contextRoot1;
 
-  late MockChannel channel;
-  late _TestServerPlugin plugin;
+  @override
+  ServerPlugin createPlugin() {
+    return _TestServerPlugin(resourceProvider);
+  }
 
-  void setUp() {
+  @override
+  Future<void> setUp() async {
+    await super.setUp();
+
     packagePath1 = convertPath('/package1');
     filePath1 = join(packagePath1, 'lib', 'test.dart');
     newFile(filePath1, '');
     contextRoot1 = ContextRoot(packagePath1, <String>[]);
-
-    channel = MockChannel();
-    plugin = _TestServerPlugin(resourceProvider);
-    plugin.start(channel);
   }
 
   Future<void> test_handleEditGetAssists() async {
diff --git a/pkg/analyzer_plugin/test/plugin/completion_mixin_test.dart b/pkg/analyzer_plugin/test/plugin/completion_mixin_test.dart
index ad201a7..9c3a563 100644
--- a/pkg/analyzer_plugin/test/plugin/completion_mixin_test.dart
+++ b/pkg/analyzer_plugin/test/plugin/completion_mixin_test.dart
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:analyzer_plugin/plugin/completion_mixin.dart';
+import 'package:analyzer_plugin/plugin/plugin.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:analyzer_plugin/protocol/protocol_generated.dart';
 import 'package:analyzer_plugin/src/utilities/completion/completion_core.dart';
@@ -13,29 +13,30 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'mocks.dart';
+import 'plugin_test.dart';
 
 void main() {
   defineReflectiveTests(CompletionMixinTest);
 }
 
 @reflectiveTest
-class CompletionMixinTest with ResourceProviderMixin {
+class CompletionMixinTest extends AbstractPluginTest {
   late String packagePath1;
   late String filePath1;
   late ContextRoot contextRoot1;
 
-  late MockChannel channel;
-  late _TestServerPlugin plugin;
+  @override
+  ServerPlugin createPlugin() {
+    return _TestServerPlugin(resourceProvider);
+  }
 
-  void setUp() {
+  @override
+  Future<void> setUp() async {
+    await super.setUp();
     packagePath1 = convertPath('/package1');
     filePath1 = join(packagePath1, 'lib', 'test.dart');
     newFile(filePath1, 'int foo = bar;');
     contextRoot1 = ContextRoot(packagePath1, <String>[]);
-
-    channel = MockChannel();
-    plugin = _TestServerPlugin(resourceProvider);
-    plugin.start(channel);
   }
 
   Future<void> test_handleCompletionGetSuggestions() async {
diff --git a/pkg/analyzer_plugin/test/plugin/fix_mixin_test.dart b/pkg/analyzer_plugin/test/plugin/fix_mixin_test.dart
index 34b456d..f095acd 100644
--- a/pkg/analyzer_plugin/test/plugin/fix_mixin_test.dart
+++ b/pkg/analyzer_plugin/test/plugin/fix_mixin_test.dart
@@ -6,8 +6,8 @@
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/generated/source.dart';
-import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:analyzer_plugin/plugin/fix_mixin.dart';
+import 'package:analyzer_plugin/plugin/plugin.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart'
     hide AnalysisError;
 import 'package:analyzer_plugin/protocol/protocol_generated.dart';
@@ -17,29 +17,30 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'mocks.dart';
+import 'plugin_test.dart';
 
 void main() {
   defineReflectiveTests(FixesMixinTest);
 }
 
 @reflectiveTest
-class FixesMixinTest with ResourceProviderMixin {
+class FixesMixinTest extends AbstractPluginTest {
   late String packagePath1;
   late String filePath1;
   late ContextRoot contextRoot1;
 
-  late MockChannel channel;
-  late _TestServerPlugin plugin;
+  @override
+  ServerPlugin createPlugin() {
+    return _TestServerPlugin(resourceProvider);
+  }
 
-  void setUp() {
+  @override
+  Future<void> setUp() async {
+    await super.setUp();
     packagePath1 = convertPath('/package1');
     filePath1 = join(packagePath1, 'lib', 'test.dart');
     newFile(filePath1, '');
     contextRoot1 = ContextRoot(packagePath1, <String>[]);
-
-    channel = MockChannel();
-    plugin = _TestServerPlugin(resourceProvider);
-    plugin.start(channel);
   }
 
   Future<void> test_handleEditGetFixes() async {
diff --git a/pkg/analyzer_plugin/test/plugin/folding_mixin_test.dart b/pkg/analyzer_plugin/test/plugin/folding_mixin_test.dart
index c1986c4..528a37d 100644
--- a/pkg/analyzer_plugin/test/plugin/folding_mixin_test.dart
+++ b/pkg/analyzer_plugin/test/plugin/folding_mixin_test.dart
@@ -5,8 +5,8 @@
 import 'dart:async';
 
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:analyzer_plugin/plugin/folding_mixin.dart';
+import 'package:analyzer_plugin/plugin/plugin.dart';
 import 'package:analyzer_plugin/protocol/protocol.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:analyzer_plugin/protocol/protocol_generated.dart';
@@ -16,29 +16,30 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'mocks.dart';
+import 'plugin_test.dart';
 
 void main() {
   defineReflectiveTests(FoldingMixinTest);
 }
 
 @reflectiveTest
-class FoldingMixinTest with ResourceProviderMixin {
+class FoldingMixinTest extends AbstractPluginTest {
   late String packagePath1;
   late String filePath1;
   late ContextRoot contextRoot1;
 
-  late MockChannel channel;
-  late _TestServerPlugin plugin;
+  @override
+  ServerPlugin createPlugin() {
+    return _TestServerPlugin(resourceProvider);
+  }
 
-  void setUp() {
+  @override
+  Future<void> setUp() async {
+    await super.setUp();
     packagePath1 = convertPath('/package1');
     filePath1 = join(packagePath1, 'lib', 'test.dart');
     newFile(filePath1, '');
     contextRoot1 = ContextRoot(packagePath1, <String>[]);
-
-    channel = MockChannel();
-    plugin = _TestServerPlugin(resourceProvider);
-    plugin.start(channel);
   }
 
   Future<void> test_sendFoldingNotification() async {
diff --git a/pkg/analyzer_plugin/test/plugin/highlights_mixin_test.dart b/pkg/analyzer_plugin/test/plugin/highlights_mixin_test.dart
index 409eafa..c7f0a33 100644
--- a/pkg/analyzer_plugin/test/plugin/highlights_mixin_test.dart
+++ b/pkg/analyzer_plugin/test/plugin/highlights_mixin_test.dart
@@ -5,8 +5,8 @@
 import 'dart:async';
 
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:analyzer_plugin/plugin/highlights_mixin.dart';
+import 'package:analyzer_plugin/plugin/plugin.dart';
 import 'package:analyzer_plugin/protocol/protocol.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:analyzer_plugin/protocol/protocol_generated.dart';
@@ -16,29 +16,30 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'mocks.dart';
+import 'plugin_test.dart';
 
 void main() {
   defineReflectiveTests(HighlightsMixinTest);
 }
 
 @reflectiveTest
-class HighlightsMixinTest with ResourceProviderMixin {
+class HighlightsMixinTest extends AbstractPluginTest {
   late String packagePath1;
   late String filePath1;
   late ContextRoot contextRoot1;
 
-  late MockChannel channel;
-  late _TestServerPlugin plugin;
+  @override
+  ServerPlugin createPlugin() {
+    return _TestServerPlugin(resourceProvider);
+  }
 
-  void setUp() {
+  @override
+  Future<void> setUp() async {
+    await super.setUp();
     packagePath1 = convertPath('/package1');
     filePath1 = join(packagePath1, 'lib', 'test.dart');
     newFile(filePath1, '');
     contextRoot1 = ContextRoot(packagePath1, <String>[]);
-
-    channel = MockChannel();
-    plugin = _TestServerPlugin(resourceProvider);
-    plugin.start(channel);
   }
 
   Future<void> test_sendHighlightsNotification() async {
diff --git a/pkg/analyzer_plugin/test/plugin/kythe_mixin_test.dart b/pkg/analyzer_plugin/test/plugin/kythe_mixin_test.dart
index e038fe0..59fccaa 100644
--- a/pkg/analyzer_plugin/test/plugin/kythe_mixin_test.dart
+++ b/pkg/analyzer_plugin/test/plugin/kythe_mixin_test.dart
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:analyzer_plugin/plugin/kythe_mixin.dart';
+import 'package:analyzer_plugin/plugin/plugin.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:analyzer_plugin/protocol/protocol_generated.dart';
 import 'package:analyzer_plugin/src/utilities/kythe/entries.dart';
@@ -13,29 +13,30 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'mocks.dart';
+import 'plugin_test.dart';
 
 void main() {
   defineReflectiveTests(KytheMixinTest);
 }
 
 @reflectiveTest
-class KytheMixinTest with ResourceProviderMixin {
+class KytheMixinTest extends AbstractPluginTest {
   late String packagePath1;
   late String filePath1;
   late ContextRoot contextRoot1;
 
-  late MockChannel channel;
-  late _TestServerPlugin plugin;
+  @override
+  ServerPlugin createPlugin() {
+    return _TestServerPlugin(resourceProvider);
+  }
 
-  void setUp() {
+  @override
+  Future<void> setUp() async {
+    await super.setUp();
     packagePath1 = convertPath('/package1');
     filePath1 = join(packagePath1, 'lib', 'test.dart');
     newFile(filePath1, '');
     contextRoot1 = ContextRoot(packagePath1, <String>[]);
-
-    channel = MockChannel();
-    plugin = _TestServerPlugin(resourceProvider);
-    plugin.start(channel);
   }
 
   Future<void> test_handleEditGetAssists() async {
@@ -44,7 +45,7 @@
 
     var result = await plugin
         .handleKytheGetKytheEntries(KytheGetKytheEntriesParams(filePath1));
-    expect(result, isNotNull);
+    result!;
     expect(result.entries, hasLength(3));
   }
 }
diff --git a/pkg/analyzer_plugin/test/plugin/mocks.dart b/pkg/analyzer_plugin/test/plugin/mocks.dart
index fd3665f..69cd682 100644
--- a/pkg/analyzer_plugin/test/plugin/mocks.dart
+++ b/pkg/analyzer_plugin/test/plugin/mocks.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import 'package:analyzer/dart/analysis/analysis_context.dart';
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/file_system/file_system.dart';
@@ -14,7 +15,6 @@
 import 'package:analyzer_plugin/channel/channel.dart';
 import 'package:analyzer_plugin/plugin/plugin.dart';
 import 'package:analyzer_plugin/protocol/protocol.dart';
-import 'package:analyzer_plugin/protocol/protocol_generated.dart';
 import 'package:analyzer_plugin/src/protocol/protocol_internal.dart';
 import 'package:test/test.dart';
 
@@ -138,7 +138,8 @@
 
 /// A concrete implementation of a server plugin that is suitable for testing.
 class MockServerPlugin extends ServerPlugin {
-  MockServerPlugin(ResourceProvider resourceProvider) : super(resourceProvider);
+  MockServerPlugin(ResourceProvider resourceProvider)
+      : super(resourceProvider: resourceProvider);
 
   @override
   List<String> get fileGlobsToAnalyze => <String>['*.dart'];
@@ -150,9 +151,10 @@
   String get version => '0.1.0';
 
   @override
-  AnalysisDriverGeneric createAnalysisDriver(ContextRoot contextRoot) {
-    return MockAnalysisDriver();
-  }
+  Future<void> analyzeFile({
+    required AnalysisContext analysisContext,
+    required String path,
+  }) async {}
 }
 
 class MockSource implements Source {
diff --git a/pkg/analyzer_plugin/test/plugin/navigation_mixin_test.dart b/pkg/analyzer_plugin/test/plugin/navigation_mixin_test.dart
index 38cb85a..3978d56 100644
--- a/pkg/analyzer_plugin/test/plugin/navigation_mixin_test.dart
+++ b/pkg/analyzer_plugin/test/plugin/navigation_mixin_test.dart
@@ -5,8 +5,8 @@
 import 'dart:async';
 
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:analyzer_plugin/plugin/navigation_mixin.dart';
+import 'package:analyzer_plugin/plugin/plugin.dart';
 import 'package:analyzer_plugin/protocol/protocol.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:analyzer_plugin/protocol/protocol_generated.dart';
@@ -16,29 +16,30 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'mocks.dart';
+import 'plugin_test.dart';
 
 void main() {
   defineReflectiveTests(NavigationMixinTest);
 }
 
 @reflectiveTest
-class NavigationMixinTest with ResourceProviderMixin {
+class NavigationMixinTest extends AbstractPluginTest {
   late String packagePath1;
   late String filePath1;
   late ContextRoot contextRoot1;
 
-  late MockChannel channel;
-  late _TestServerPlugin plugin;
+  @override
+  ServerPlugin createPlugin() {
+    return _TestServerPlugin(resourceProvider);
+  }
 
-  void setUp() {
+  @override
+  Future<void> setUp() async {
+    await super.setUp();
     packagePath1 = convertPath('/package1');
     filePath1 = join(packagePath1, 'lib', 'test.dart');
     newFile(filePath1, '');
     contextRoot1 = ContextRoot(packagePath1, <String>[]);
-
-    channel = MockChannel();
-    plugin = _TestServerPlugin(resourceProvider);
-    plugin.start(channel);
   }
 
   Future<void> test_handleAnalysisGetNavigation() async {
diff --git a/pkg/analyzer_plugin/test/plugin/occurrences_mixin_test.dart b/pkg/analyzer_plugin/test/plugin/occurrences_mixin_test.dart
index fa1aec6..c44404a 100644
--- a/pkg/analyzer_plugin/test/plugin/occurrences_mixin_test.dart
+++ b/pkg/analyzer_plugin/test/plugin/occurrences_mixin_test.dart
@@ -5,8 +5,8 @@
 import 'dart:async';
 
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:analyzer_plugin/plugin/occurrences_mixin.dart';
+import 'package:analyzer_plugin/plugin/plugin.dart';
 import 'package:analyzer_plugin/protocol/protocol.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:analyzer_plugin/protocol/protocol_generated.dart';
@@ -16,29 +16,30 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'mocks.dart';
+import 'plugin_test.dart';
 
 void main() {
   defineReflectiveTests(OccurrencesMixinTest);
 }
 
 @reflectiveTest
-class OccurrencesMixinTest with ResourceProviderMixin {
+class OccurrencesMixinTest extends AbstractPluginTest {
   late String packagePath1;
   late String filePath1;
   late ContextRoot contextRoot1;
 
-  late MockChannel channel;
-  late _TestServerPlugin plugin;
+  @override
+  ServerPlugin createPlugin() {
+    return _TestServerPlugin(resourceProvider);
+  }
 
-  void setUp() {
+  @override
+  Future<void> setUp() async {
+    await super.setUp();
     packagePath1 = convertPath('/package1');
     filePath1 = join(packagePath1, 'lib', 'test.dart');
     newFile(filePath1, '');
     contextRoot1 = ContextRoot(packagePath1, <String>[]);
-
-    channel = MockChannel();
-    plugin = _TestServerPlugin(resourceProvider);
-    plugin.start(channel);
   }
 
   Future<void> test_sendOccurrencesNotification() async {
diff --git a/pkg/analyzer_plugin/test/plugin/outline_mixin_test.dart b/pkg/analyzer_plugin/test/plugin/outline_mixin_test.dart
index 1b4ff4e..55a632d 100644
--- a/pkg/analyzer_plugin/test/plugin/outline_mixin_test.dart
+++ b/pkg/analyzer_plugin/test/plugin/outline_mixin_test.dart
@@ -5,8 +5,8 @@
 import 'dart:async';
 
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:analyzer_plugin/plugin/outline_mixin.dart';
+import 'package:analyzer_plugin/plugin/plugin.dart';
 import 'package:analyzer_plugin/protocol/protocol.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:analyzer_plugin/protocol/protocol_generated.dart';
@@ -16,29 +16,30 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'mocks.dart';
+import 'plugin_test.dart';
 
 void main() {
   defineReflectiveTests(OutlineMixinTest);
 }
 
 @reflectiveTest
-class OutlineMixinTest with ResourceProviderMixin {
+class OutlineMixinTest extends AbstractPluginTest {
   late String packagePath1;
   late String filePath1;
   late ContextRoot contextRoot1;
 
-  late MockChannel channel;
-  late _TestServerPlugin plugin;
+  @override
+  ServerPlugin createPlugin() {
+    return _TestServerPlugin(resourceProvider);
+  }
 
-  void setUp() {
+  @override
+  Future<void> setUp() async {
+    await super.setUp();
     packagePath1 = convertPath('/package1');
     filePath1 = join(packagePath1, 'lib', 'test.dart');
     newFile(filePath1, '');
     contextRoot1 = ContextRoot(packagePath1, <String>[]);
-
-    channel = MockChannel();
-    plugin = _TestServerPlugin(resourceProvider);
-    plugin.start(channel);
   }
 
   Future<void> test_sendOutlineNotification() async {
diff --git a/pkg/analyzer_plugin/test/plugin/plugin_test.dart b/pkg/analyzer_plugin/test/plugin/plugin_test.dart
index a221850..a398fa6 100644
--- a/pkg/analyzer_plugin/test/plugin/plugin_test.dart
+++ b/pkg/analyzer_plugin/test/plugin/plugin_test.dart
@@ -2,12 +2,17 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:analyzer/dart/analysis/analysis_context.dart';
+import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/dart/analysis/driver.dart';
+import 'package:analyzer/src/test_utilities/mock_sdk.dart';
 import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
+import 'package:analyzer_plugin/plugin/plugin.dart';
 import 'package:analyzer_plugin/protocol/protocol.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:analyzer_plugin/protocol/protocol_generated.dart';
+import 'package:meta/meta.dart';
+import 'package:pub_semver/pub_semver.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -17,11 +22,40 @@
   defineReflectiveTests(ServerPluginTest);
 }
 
-@reflectiveTest
-class ServerPluginTest with ResourceProviderMixin {
-  late MockChannel channel;
-  late _TestServerPlugin plugin;
+abstract class AbstractPluginTest with ResourceProviderMixin {
+  final MockChannel channel = MockChannel();
+  late final ServerPlugin plugin;
 
+  Folder get byteStoreRoot => getFolder('/byteStore');
+
+  Version get pluginSpecificationVersion => Version(0, 1, 0);
+
+  Folder get sdkRoot => getFolder('/sdk');
+
+  ServerPlugin createPlugin();
+
+  @mustCallSuper
+  Future<void> setUp() async {
+    createMockSdk(
+      resourceProvider: resourceProvider,
+      root: sdkRoot,
+    );
+
+    plugin = createPlugin();
+    plugin.start(channel);
+
+    await plugin.handlePluginVersionCheck(
+      PluginVersionCheckParams(
+        byteStoreRoot.path,
+        sdkRoot.path,
+        pluginSpecificationVersion.canonicalizedVersion,
+      ),
+    );
+  }
+}
+
+@reflectiveTest
+class ServerPluginTest extends AbstractPluginTest {
   late String packagePath1;
   late String filePath1;
   late ContextRoot contextRoot1;
@@ -30,7 +64,14 @@
   late String filePath2;
   late ContextRoot contextRoot2;
 
-  void setUp() {
+  @override
+  ServerPlugin createPlugin() {
+    return _TestServerPlugin(resourceProvider);
+  }
+
+  @override
+  Future<void> setUp() async {
+    await super.setUp();
     packagePath1 = convertPath('/package1');
     filePath1 = join(packagePath1, 'lib', 'test.dart');
     newFile(filePath1, '');
@@ -40,28 +81,6 @@
     filePath2 = join(packagePath2, 'lib', 'test.dart');
     newFile(filePath2, '');
     contextRoot2 = ContextRoot(packagePath2, <String>[]);
-
-    channel = MockChannel();
-    plugin = _TestServerPlugin(resourceProvider);
-    plugin.start(channel);
-  }
-
-  Future<void> test_contextRootContaining_insideRoot() async {
-    await plugin.handleAnalysisSetContextRoots(
-        AnalysisSetContextRootsParams([contextRoot1]));
-
-    expect(plugin.contextRootContaining(filePath1), isNotNull);
-  }
-
-  void test_contextRootContaining_noRoots() {
-    expect(plugin.contextRootContaining(filePath1), isNull);
-  }
-
-  Future<void> test_contextRootContaining_outsideRoot() async {
-    await plugin.handleAnalysisSetContextRoots(
-        AnalysisSetContextRootsParams([contextRoot1]));
-
-    expect(plugin.contextRootContaining(filePath2), isNull);
   }
 
   Future<void> test_handleAnalysisGetNavigation() async {
@@ -76,15 +95,6 @@
     expect(result, isNotNull);
   }
 
-  Future<void> test_handleAnalysisSetContextRoots() async {
-    var result = await plugin.handleAnalysisSetContextRoots(
-        AnalysisSetContextRootsParams([contextRoot1]));
-    expect(result, isNotNull);
-    var driver = _getDriver(contextRoot1);
-    expect(driver, isNotNull);
-    expect((driver as MockAnalysisDriver).addedFiles, hasLength(1));
-  }
-
   Future<void> test_handleAnalysisSetPriorityFiles() async {
     await plugin.handleAnalysisSetContextRoots(
         AnalysisSetContextRootsParams([contextRoot1]));
@@ -239,12 +249,22 @@
   }
 
   Future<void> test_onRequest_analysisSetContextRoots() async {
+    final plugin = this.plugin as _TestServerPlugin;
+
+    final analyzedPaths = <String>[];
+    plugin.analyzeFileHandler = ({
+      required AnalysisContext analysisContext,
+      required String path,
+    }) {
+      analyzedPaths.add(path);
+    };
+
     var result = await channel
         .sendRequest(AnalysisSetContextRootsParams([contextRoot1]));
     expect(result, isNotNull);
-    var driver = _getDriver(contextRoot1);
-    expect(driver, isNotNull);
-    expect((driver as MockAnalysisDriver).addedFiles, hasLength(1));
+
+    expect(plugin.invoked_afterNewContextCollection, isTrue);
+    expect(analyzedPaths, [getFile('/package1/lib/test.dart').path]);
   }
 
   Future<void> test_onRequest_analysisSetPriorityFiles() async {
@@ -346,7 +366,7 @@
       service3: [filePath2]
     });
     plugin.sendNotificationsForFile(filePath1);
-    var notifications = plugin.sentNotifications;
+    var notifications = (plugin as _TestServerPlugin).sentNotifications;
     expect(notifications, hasLength(1));
     var services = notifications[filePath1];
     expect(services, unorderedEquals([service1, service2]));
@@ -356,7 +376,7 @@
     var subscriptions = <String, List<AnalysisService>>{};
 
     plugin.sendNotificationsForSubscriptions(subscriptions);
-    var notifications = plugin.sentNotifications;
+    var notifications = (plugin as _TestServerPlugin).sentNotifications;
     expect(notifications, hasLength(subscriptions.length));
     for (var path in subscriptions.keys) {
       var subscribedServices = subscriptions[path];
@@ -367,25 +387,44 @@
           reason: 'Wrong notifications for file $path');
     }
   }
-
-  AnalysisDriverGeneric? _getDriver(ContextRoot targetRoot) {
-    for (var root in plugin.driverMap.keys) {
-      if (root.root == targetRoot.root) {
-        return plugin.driverMap[root];
-      }
-    }
-    return null;
-  }
 }
 
 class _TestServerPlugin extends MockServerPlugin {
   Map<String, List<AnalysisService>> sentNotifications =
       <String, List<AnalysisService>>{};
 
+  bool invoked_afterNewContextCollection = false;
+
+  void Function({
+    required AnalysisContext analysisContext,
+    required String path,
+  })? analyzeFileHandler;
+
   _TestServerPlugin(ResourceProvider resourceProvider)
       : super(resourceProvider);
 
   @override
+  Future<void> afterNewContextCollection({
+    required AnalysisContextCollection contextCollection,
+  }) async {
+    invoked_afterNewContextCollection = true;
+    return super.afterNewContextCollection(
+      contextCollection: contextCollection,
+    );
+  }
+
+  @override
+  Future<void> analyzeFile({
+    required AnalysisContext analysisContext,
+    required String path,
+  }) async {
+    analyzeFileHandler?.call(
+      analysisContext: analysisContext,
+      path: path,
+    );
+  }
+
+  @override
   Future<void> sendFoldingNotification(String path) {
     _sent(path, AnalysisService.FOLDING);
     return Future.value();
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index d562113..7b61cef0 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -559,7 +559,10 @@
       }
     } else {
       closedWorldAndIndices = await serializationTask.deserializeClosedWorld(
-          environment, abstractValueStrategy, component);
+          environment,
+          abstractValueStrategy,
+          component,
+          useDeferredSourceReads);
     }
     if (closedWorldAndIndices != null && retainDataForTesting) {
       backendClosedWorldForTesting = closedWorldAndIndices.data;
@@ -568,13 +571,16 @@
     return closedWorldAndIndices;
   }
 
+  bool get shouldStopAfterClosedWorldFromFlags =>
+      stopAfterClosedWorldForTesting ||
+      options.stopAfterProgramSplit ||
+      options.writeClosedWorldUri != null;
+
   bool shouldStopAfterClosedWorld(
           DataAndIndices<JsClosedWorld> closedWorldAndIndices) =>
       closedWorldAndIndices == null ||
       closedWorldAndIndices.data == null ||
-      stopAfterClosedWorldForTesting ||
-      options.stopAfterProgramSplit ||
-      options.writeClosedWorldUri != null;
+      shouldStopAfterClosedWorldFromFlags;
 
   Future<DataAndIndices<GlobalTypeInferenceResults>>
       produceGlobalTypeInferenceResults(
@@ -597,7 +603,8 @@
               environment,
               abstractValueStrategy,
               closedWorld.elementMap.programEnv.mainComponent,
-              closedWorldAndIndices);
+              closedWorldAndIndices,
+              useDeferredSourceReads);
     }
     return globalTypeInferenceResults;
   }
@@ -631,13 +638,20 @@
           backendStrategy,
           globalTypeInferenceResults.data,
           codegenInputs,
-          globalTypeInferenceResults.indices);
+          globalTypeInferenceResults.indices,
+          useDeferredSourceReads);
     }
     return codegenResults;
   }
 
   bool get shouldStopAfterCodegen => options.writeCodegenUri != null;
 
+  bool get useDeferredSourceReads =>
+      !shouldStopAfterClosedWorldFromFlags &&
+      !shouldStopAfterGlobalTypeInference &&
+      !shouldStopAfterCodegen &&
+      !shouldStopAfterModularAnalysis;
+
   void runSequentialPhases() async {
     // Load kernel.
     load_kernel.Output output = await produceKernel();
diff --git a/pkg/compiler/lib/src/inferrer/abstract_value_strategy.dart b/pkg/compiler/lib/src/inferrer/abstract_value_strategy.dart
index de87183..87d5b7d1 100644
--- a/pkg/compiler/lib/src/inferrer/abstract_value_strategy.dart
+++ b/pkg/compiler/lib/src/inferrer/abstract_value_strategy.dart
@@ -4,7 +4,7 @@
 
 // @dart = 2.10
 
-import '../universe/world_builder.dart';
+import '../universe/world_builder.dart' show SelectorConstraintsStrategy;
 import '../world.dart';
 import 'abstract_value_domain.dart';
 
diff --git a/pkg/compiler/lib/src/inferrer/types.dart b/pkg/compiler/lib/src/inferrer/types.dart
index 92ee1c5..8705fb2 100644
--- a/pkg/compiler/lib/src/inferrer/types.dart
+++ b/pkg/compiler/lib/src/inferrer/types.dart
@@ -19,6 +19,7 @@
 import '../js_model/element_map.dart';
 import '../js_model/js_world.dart';
 import '../js_model/locals.dart';
+import '../serialization/deferrable.dart';
 import '../serialization/serialization.dart';
 import '../universe/selector.dart' show Selector;
 import '../world.dart' show JClosedWorld;
@@ -219,17 +220,35 @@
   final GlobalTypeInferenceMemberResult _deadMethodResult;
   final AbstractValue _trivialParameterResult;
 
-  final Map<MemberEntity, GlobalTypeInferenceMemberResult> memberResults;
-  final Map<Local, AbstractValue> parameterResults;
+  final Deferrable<Map<MemberEntity, GlobalTypeInferenceMemberResult>>
+      _memberResults;
+  final Deferrable<Map<Local, AbstractValue>> _parameterResults;
   final Set<Selector> returnsListElementTypeSet;
-  final Map<ir.TreeNode, AbstractValue> _allocatedLists;
+  final Deferrable<Map<ir.TreeNode, AbstractValue>> _allocatedLists;
 
   GlobalTypeInferenceResultsImpl(
       this.closedWorld,
       this.globalLocalsMap,
       this.inferredData,
-      this.memberResults,
-      this.parameterResults,
+      Map<MemberEntity, GlobalTypeInferenceMemberResult> memberResults,
+      Map<Local, AbstractValue> parameterResults,
+      this.returnsListElementTypeSet,
+      Map<ir.TreeNode, AbstractValue> allocatedLists)
+      : _memberResults = Deferrable.eager(memberResults),
+        _parameterResults = Deferrable.eager(parameterResults),
+        _allocatedLists = Deferrable.eager(allocatedLists),
+        _deadFieldResult =
+            DeadFieldGlobalTypeInferenceResult(closedWorld.abstractValueDomain),
+        _deadMethodResult = DeadMethodGlobalTypeInferenceResult(
+            closedWorld.abstractValueDomain),
+        _trivialParameterResult = closedWorld.abstractValueDomain.dynamicType;
+
+  GlobalTypeInferenceResultsImpl._deserialized(
+      this.closedWorld,
+      this.globalLocalsMap,
+      this.inferredData,
+      this._memberResults,
+      this._parameterResults,
       this.returnsListElementTypeSet,
       this._allocatedLists)
       : _deadFieldResult =
@@ -247,22 +266,25 @@
     source.registerLocalLookup(LocalLookupImpl(globalLocalsMap));
 
     source.begin(tag);
-    Map<MemberEntity, GlobalTypeInferenceMemberResult> memberResults =
-        source.readMemberMap((MemberEntity member) =>
-            GlobalTypeInferenceMemberResult.readFromDataSource(
-                source,
-                elementMap.getMemberContextNode(member),
-                closedWorld.abstractValueDomain));
-    Map<Local, AbstractValue> parameterResults = source.readLocalMap(() =>
-        closedWorld.abstractValueDomain
-            .readAbstractValueFromDataSource(source));
+    Deferrable<Map<MemberEntity, GlobalTypeInferenceMemberResult>>
+        memberResults = source.readDeferrable(() => source.readMemberMap(
+            (MemberEntity member) =>
+                GlobalTypeInferenceMemberResult.readFromDataSource(
+                    source,
+                    elementMap.getMemberContextNode(member),
+                    closedWorld.abstractValueDomain)));
+    Deferrable<Map<Local, AbstractValue>> parameterResults =
+        source.readDeferrable(() => source.readLocalMap(() => closedWorld
+            .abstractValueDomain
+            .readAbstractValueFromDataSource(source)));
     Set<Selector> returnsListElementTypeSet =
         source.readList(() => Selector.readFromDataSource(source)).toSet();
-    Map<ir.TreeNode, AbstractValue> allocatedLists = source.readTreeNodeMap(
-        () => closedWorld.abstractValueDomain
-            .readAbstractValueFromDataSource(source));
+    Deferrable<Map<ir.TreeNode, AbstractValue>> allocatedLists =
+        source.readDeferrable(() => source.readTreeNodeMap(() => closedWorld
+            .abstractValueDomain
+            .readAbstractValueFromDataSource(source)));
     source.end(tag);
-    return GlobalTypeInferenceResultsImpl(
+    return GlobalTypeInferenceResultsImpl._deserialized(
         closedWorld,
         globalLocalsMap,
         inferredData,
@@ -276,23 +298,23 @@
   void writeToDataSink(DataSinkWriter sink, JsToElementMap elementMap) {
     sink.writeBool(false); // Is _not_ trivial.
     sink.begin(tag);
-    sink.writeMemberMap(
-        memberResults,
+    sink.writeDeferrable(() => sink.writeMemberMap(
+        _memberResults.loaded(),
         (MemberEntity member, GlobalTypeInferenceMemberResult result) =>
             result.writeToDataSink(
                 sink,
                 elementMap.getMemberContextNode(member),
-                closedWorld.abstractValueDomain));
-    sink.writeLocalMap(
-        parameterResults,
+                closedWorld.abstractValueDomain)));
+    sink.writeDeferrable(() => sink.writeLocalMap(
+        _parameterResults.loaded(),
         (AbstractValue value) => closedWorld.abstractValueDomain
-            .writeAbstractValueToDataSink(sink, value));
+            .writeAbstractValueToDataSink(sink, value)));
     sink.writeList(returnsListElementTypeSet,
         (Selector selector) => selector.writeToDataSink(sink));
-    sink.writeTreeNodeMap(
-        _allocatedLists,
+    sink.writeDeferrable(() => sink.writeTreeNodeMap(
+        _allocatedLists.loaded(),
         (AbstractValue value) => closedWorld.abstractValueDomain
-            .writeAbstractValueToDataSink(sink, value));
+            .writeAbstractValueToDataSink(sink, value)));
     sink.end(tag);
   }
 
@@ -308,7 +330,7 @@
     // don't exist..
     /*assert(memberResults.containsKey(member) || member is JSignatureMethod,
         "No inference result for member $member");*/
-    return memberResults[member] ??
+    return _memberResults.loaded()[member] ??
         (member is FunctionEntity ? _deadMethodResult : _deadFieldResult);
   }
 
@@ -318,7 +340,7 @@
     // don't exist.
     /*assert(parameterResults.containsKey(parameter),
         "No inference result for parameter $parameter");*/
-    return parameterResults[parameter] ?? _trivialParameterResult;
+    return _parameterResults.loaded()[parameter] ?? _trivialParameterResult;
   }
 
   @override
@@ -399,10 +421,11 @@
   }
 
   @override
-  AbstractValue typeOfNewList(ir.Node node) => _allocatedLists[node];
+  AbstractValue typeOfNewList(ir.Node node) => _allocatedLists.loaded()[node];
 
   @override
-  AbstractValue typeOfListLiteral(ir.Node node) => _allocatedLists[node];
+  AbstractValue typeOfListLiteral(ir.Node node) =>
+      _allocatedLists.loaded()[node];
 }
 
 class GlobalTypeInferenceMemberResultImpl
diff --git a/pkg/compiler/lib/src/js_backend/backend_usage.dart b/pkg/compiler/lib/src/js_backend/backend_usage.dart
index 30138eb..9b41b8e 100644
--- a/pkg/compiler/lib/src/js_backend/backend_usage.dart
+++ b/pkg/compiler/lib/src/js_backend/backend_usage.dart
@@ -9,7 +9,7 @@
 import '../elements/entities.dart';
 import '../elements/types.dart';
 import '../ir/runtime_type_analysis.dart';
-import '../kernel/kernel_strategy.dart';
+import '../kernel/kernel_strategy.dart' show KernelFrontendStrategy;
 import '../serialization/serialization_interfaces.dart';
 import '../universe/feature.dart';
 import '../util/util.dart' show Setlet;
diff --git a/pkg/compiler/lib/src/js_backend/no_such_method_registry.dart b/pkg/compiler/lib/src/js_backend/no_such_method_registry.dart
index c62f4a3..8069eda 100644
--- a/pkg/compiler/lib/src/js_backend/no_such_method_registry.dart
+++ b/pkg/compiler/lib/src/js_backend/no_such_method_registry.dart
@@ -8,7 +8,7 @@
 import '../common/elements.dart' show CommonElements;
 import '../common/names.dart' show Identifiers, Selectors;
 import '../elements/entities.dart';
-import '../inferrer/types.dart';
+import '../inferrer/types.dart' show GlobalTypeInferenceResults;
 import '../kernel/no_such_method_resolver.dart';
 import '../serialization/serialization_interfaces.dart';
 
diff --git a/pkg/compiler/lib/src/js_backend/specialized_checks.dart b/pkg/compiler/lib/src/js_backend/specialized_checks.dart
index 3c6558b..0dbc18d 100644
--- a/pkg/compiler/lib/src/js_backend/specialized_checks.dart
+++ b/pkg/compiler/lib/src/js_backend/specialized_checks.dart
@@ -9,7 +9,6 @@
 import '../elements/entities.dart';
 import '../elements/types.dart';
 import '../js_backend/interceptor_data.dart' show InterceptorData;
-import '../ssa/nodes.dart' show HGraph;
 import '../universe/class_hierarchy.dart' show ClassHierarchy;
 import '../world.dart' show JClosedWorld;
 
@@ -26,20 +25,20 @@
 
 class SpecializedChecks {
   static IsTestSpecialization findIsTestSpecialization(
-      DartType dartType, HGraph graph, JClosedWorld closedWorld) {
+      DartType dartType, MemberEntity compiland, JClosedWorld closedWorld) {
     if (dartType is LegacyType) {
       DartType base = dartType.baseType;
       // `Never*` accepts only `null`.
       if (base is NeverType) return IsTestSpecialization.isNull;
       // `Object*` is top and should be handled by constant folding.
       if (base.isObject) return null;
-      return _findIsTestSpecialization(base, graph, closedWorld);
+      return _findIsTestSpecialization(base, compiland, closedWorld);
     }
-    return _findIsTestSpecialization(dartType, graph, closedWorld);
+    return _findIsTestSpecialization(dartType, compiland, closedWorld);
   }
 
   static IsTestSpecialization _findIsTestSpecialization(
-      DartType dartType, HGraph graph, JClosedWorld closedWorld) {
+      DartType dartType, MemberEntity compiland, JClosedWorld closedWorld) {
     if (dartType is InterfaceType) {
       ClassEntity element = dartType.element;
       JCommonElements commonElements = closedWorld.commonElements;
@@ -101,7 +100,7 @@
           classHierarchy.isInstantiated(element) &&
           !interceptorData.isInterceptedClass(element) &&
           outputUnitData.hasOnlyNonDeferredImportPathsToClass(
-              graph.element, element)) {
+              compiland, element)) {
         assert(!dartType.isObject); // Checked above.
         return IsTestSpecialization.instanceof;
       }
diff --git a/pkg/compiler/lib/src/js_model/closure.dart b/pkg/compiler/lib/src/js_model/closure.dart
index fad1f00..a08b941 100644
--- a/pkg/compiler/lib/src/js_model/closure.dart
+++ b/pkg/compiler/lib/src/js_model/closure.dart
@@ -19,9 +19,11 @@
 import '../js_model/element_map.dart';
 import '../js_model/env.dart';
 import '../ordered_typeset.dart';
+import '../serialization/deferrable.dart';
 import '../serialization/serialization.dart';
 import '../universe/selector.dart';
 import 'elements.dart';
+import 'jrecord_field_interface.dart';
 import 'js_world_builder.dart' show JsClosedWorldBuilder;
 
 class ClosureDataImpl implements ClosureData {
@@ -32,19 +34,35 @@
   final JsToElementMap _elementMap;
 
   /// Map of the scoping information that corresponds to a particular entity.
-  final Map<MemberEntity, ScopeInfo> _scopeMap;
-  final Map<ir.TreeNode, CapturedScope> _capturedScopesMap;
+  final Deferrable<Map<MemberEntity, ScopeInfo>> _scopeMap;
+  final Deferrable<Map<ir.TreeNode, CapturedScope>> _capturedScopesMap;
   // Indicates the type variables (if any) that are captured in a given
   // Signature function.
-  final Map<MemberEntity, CapturedScope> _capturedScopeForSignatureMap;
+  final Deferrable<Map<MemberEntity, CapturedScope>>
+      _capturedScopeForSignatureMap;
 
-  final Map<ir.LocalFunction, ClosureRepresentationInfo>
+  final Deferrable<Map<ir.LocalFunction, ClosureRepresentationInfo>>
       _localClosureRepresentationMap;
 
   final Map<MemberEntity, MemberEntity> _enclosingMembers;
 
   ClosureDataImpl(
       this._elementMap,
+      Map<MemberEntity, ScopeInfo> scopeMap,
+      Map<ir.TreeNode, CapturedScope> capturedScopesMap,
+      Map<MemberEntity, CapturedScope> capturedScopeForSignatureMap,
+      Map<ir.LocalFunction, ClosureRepresentationInfo>
+          localClosureRepresentationMap,
+      this._enclosingMembers)
+      : _scopeMap = Deferrable.eager(scopeMap),
+        _capturedScopesMap = Deferrable.eager(capturedScopesMap),
+        _capturedScopeForSignatureMap =
+            Deferrable.eager(capturedScopeForSignatureMap),
+        _localClosureRepresentationMap =
+            Deferrable.eager(localClosureRepresentationMap);
+
+  ClosureDataImpl._deserialized(
+      this._elementMap,
       this._scopeMap,
       this._capturedScopesMap,
       this._capturedScopeForSignatureMap,
@@ -56,20 +74,20 @@
       JsToElementMap elementMap, DataSourceReader source) {
     source.begin(tag);
     // TODO(johnniwinther): Support shared [ScopeInfo].
-    Map<MemberEntity, ScopeInfo> scopeMap = source.readMemberMap(
-        (MemberEntity member) => ScopeInfo.readFromDataSource(source));
-    Map<ir.TreeNode, CapturedScope> capturedScopesMap =
-        source.readTreeNodeMap(() => CapturedScope.readFromDataSource(source));
-    Map<MemberEntity, CapturedScope> capturedScopeForSignatureMap =
+    final scopeMap = source.readDeferrable(() => source.readMemberMap(
+        (MemberEntity member) => ScopeInfo.readFromDataSource(source)));
+    final capturedScopesMap = source.readDeferrable(() =>
+        source.readTreeNodeMap(() => CapturedScope.readFromDataSource(source)));
+    final capturedScopeForSignatureMap = source.readDeferrable(() =>
         source.readMemberMap(
-            (MemberEntity member) => CapturedScope.readFromDataSource(source));
-    Map<ir.LocalFunction, ClosureRepresentationInfo>
-        localClosureRepresentationMap = source.readTreeNodeMap(
-            () => ClosureRepresentationInfo.readFromDataSource(source));
+            (MemberEntity member) => CapturedScope.readFromDataSource(source)));
+    final localClosureRepresentationMap = source.readDeferrable(() =>
+        source.readTreeNodeMap<ir.LocalFunction, ClosureRepresentationInfo>(
+            () => ClosureRepresentationInfo.readFromDataSource(source)));
     Map<MemberEntity, MemberEntity> enclosingMembers =
         source.readMemberMap((member) => source.readMember());
     source.end(tag);
-    return ClosureDataImpl(
+    return ClosureDataImpl._deserialized(
         elementMap,
         scopeMap,
         capturedScopesMap,
@@ -82,19 +100,21 @@
   @override
   void writeToDataSink(DataSinkWriter sink) {
     sink.begin(tag);
-    sink.writeMemberMap(_scopeMap,
-        (MemberEntity member, ScopeInfo info) => info.writeToDataSink(sink));
-    sink.writeTreeNodeMap(_capturedScopesMap, (CapturedScope scope) {
-      scope.writeToDataSink(sink);
-    });
-    sink.writeMemberMap(
-        _capturedScopeForSignatureMap,
+    sink.writeDeferrable(() => sink.writeMemberMap(_scopeMap.loaded(),
+        (MemberEntity member, ScopeInfo info) => info.writeToDataSink(sink)));
+    sink.writeDeferrable(() => sink.writeTreeNodeMap(
+            _capturedScopesMap.loaded(), (CapturedScope scope) {
+          scope.writeToDataSink(sink);
+        }));
+    sink.writeDeferrable(() => sink.writeMemberMap(
+        _capturedScopeForSignatureMap.loaded(),
         (MemberEntity member, CapturedScope scope) =>
-            scope.writeToDataSink(sink));
-    sink.writeTreeNodeMap(_localClosureRepresentationMap,
-        (ClosureRepresentationInfo info) {
-      info.writeToDataSink(sink);
-    });
+            scope.writeToDataSink(sink)));
+    sink.writeDeferrable(() => sink
+            .writeTreeNodeMap(_localClosureRepresentationMap.loaded(),
+                (ClosureRepresentationInfo info) {
+          info.writeToDataSink(sink);
+        }));
     sink.writeMemberMap(_enclosingMembers,
         (MemberEntity member, MemberEntity value) {
       sink.writeMember(value);
@@ -112,7 +132,7 @@
       entity = constructorBody.constructor;
     }
 
-    ScopeInfo scopeInfo = _scopeMap[entity];
+    ScopeInfo scopeInfo = _scopeMap.loaded()[entity];
     assert(
         scopeInfo != null, failedAt(entity, "Missing scope info for $entity."));
     return scopeInfo;
@@ -128,9 +148,11 @@
       case MemberKind.constructor:
       case MemberKind.constructorBody:
       case MemberKind.closureCall:
-        return _capturedScopesMap[definition.node] ?? const CapturedScope();
+        return _capturedScopesMap.loaded()[definition.node] ??
+            const CapturedScope();
       case MemberKind.signature:
-        return _capturedScopeForSignatureMap[entity] ?? const CapturedScope();
+        return _capturedScopeForSignatureMap.loaded()[entity] ??
+            const CapturedScope();
       default:
         throw failedAt(entity, "Unexpected member definition $definition");
     }
@@ -140,15 +162,15 @@
   // TODO(efortuna): Eventually capturedScopesMap[node] should always
   // be non-null, and we should just test that with an assert.
   CapturedLoopScope getCapturedLoopScope(ir.Node loopNode) =>
-      _capturedScopesMap[loopNode] ?? const CapturedLoopScope();
+      _capturedScopesMap.loaded()[loopNode] ?? const CapturedLoopScope();
 
   @override
   ClosureRepresentationInfo getClosureInfo(ir.LocalFunction node) {
-    var closure = _localClosureRepresentationMap[node];
+    var closure = _localClosureRepresentationMap.loaded()[node];
     assert(
         closure != null,
         "Corresponding closure class not found for $node. "
-        "Closures found for ${_localClosureRepresentationMap.keys}");
+        "Closures found for ${_localClosureRepresentationMap.loaded().keys}");
     return closure;
   }
 
@@ -1065,7 +1087,7 @@
 /// A variable that has been "boxed" to prevent name shadowing with the
 /// original variable and ensure that this variable is updated/read with the
 /// most recent value.
-class JRecordField extends JField {
+class JRecordField extends JField implements JRecordFieldInterface {
   /// Tag used for identifying serialized [JRecordField] objects in a
   /// debugging data stream.
   static const String tag = 'record-field';
@@ -1209,7 +1231,8 @@
 
   final FunctionType functionType;
   @override
-  final ir.FunctionNode functionNode;
+  ir.FunctionNode get functionNode => _functionNode.loaded();
+  final Deferrable<ir.FunctionNode> _functionNode;
   @override
   final ClassTypeVariableAccess classTypeVariableAccess;
 
@@ -1219,7 +1242,16 @@
       ClosureMemberDefinition definition,
       InterfaceType memberThisType,
       this.functionType,
-      this.functionNode,
+      ir.FunctionNode functionNode,
+      this.classTypeVariableAccess)
+      : _functionNode = Deferrable.eager(functionNode),
+        super(definition, memberThisType);
+
+  ClosureFunctionData._deserialized(
+      ClosureMemberDefinition definition,
+      InterfaceType memberThisType,
+      this.functionType,
+      this._functionNode,
       this.classTypeVariableAccess)
       : super(definition, memberThisType);
 
@@ -1230,12 +1262,13 @@
     InterfaceType /*?*/ memberThisType =
         source.readDartTypeOrNull() as InterfaceType /*?*/;
     FunctionType functionType = source.readDartType();
-    ir.FunctionNode functionNode = source.readTreeNode();
+    Deferrable<ir.FunctionNode> functionNode =
+        source.readDeferrable(() => source.readTreeNode());
     ClassTypeVariableAccess classTypeVariableAccess =
         source.readEnum(ClassTypeVariableAccess.values);
     source.end(tag);
-    return ClosureFunctionData(definition, memberThisType, functionType,
-        functionNode, classTypeVariableAccess);
+    return ClosureFunctionData._deserialized(definition, memberThisType,
+        functionType, functionNode, classTypeVariableAccess);
   }
 
   @override
@@ -1245,7 +1278,7 @@
     definition.writeToDataSink(sink);
     sink.writeDartTypeOrNull(memberThisType);
     sink.writeDartType(functionType);
-    sink.writeTreeNode(functionNode);
+    sink.writeDeferrable(() => sink.writeTreeNode(functionNode));
     sink.writeEnum(classTypeVariableAccess);
     sink.end(tag);
   }
@@ -1338,9 +1371,15 @@
   @override
   final MemberKind kind;
   @override
-  final ir.TreeNode node;
+  ir.TreeNode get node => _node.loaded();
+  final Deferrable<ir.TreeNode> _node;
 
-  ClosureMemberDefinition(this.location, this.kind, this.node)
+  ClosureMemberDefinition(this.location, this.kind, ir.TreeNode node)
+      : _node = Deferrable.eager(node),
+        assert(
+            kind == MemberKind.closureCall || kind == MemberKind.closureField);
+
+  ClosureMemberDefinition._deserialized(this.location, this.kind, this._node)
       : assert(
             kind == MemberKind.closureCall || kind == MemberKind.closureField);
 
@@ -1348,9 +1387,10 @@
       DataSourceReader source, MemberKind kind) {
     source.begin(tag);
     SourceSpan location = source.readSourceSpan();
-    ir.TreeNode node = source.readTreeNode();
+    Deferrable<ir.TreeNode> node =
+        source.readDeferrable(() => source.readTreeNode());
     source.end(tag);
-    return ClosureMemberDefinition(location, kind, node);
+    return ClosureMemberDefinition._deserialized(location, kind, node);
   }
 
   @override
@@ -1358,7 +1398,7 @@
     sink.writeEnum(kind);
     sink.begin(tag);
     sink.writeSourceSpan(location);
-    sink.writeTreeNode(node);
+    sink.writeDeferrable(() => sink.writeTreeNode(node));
     sink.end(tag);
   }
 
diff --git a/pkg/compiler/lib/src/js_model/element_map.dart b/pkg/compiler/lib/src/js_model/element_map.dart
index 9ba22fe..5de2c8d 100644
--- a/pkg/compiler/lib/src/js_model/element_map.dart
+++ b/pkg/compiler/lib/src/js_model/element_map.dart
@@ -20,6 +20,7 @@
 import '../js_model/class_type_variable_access.dart';
 import '../js_model/elements.dart' show JGeneratorBody;
 import '../native/behavior.dart';
+import '../serialization/deferrable.dart';
 import '../serialization/serialization.dart';
 import '../universe/call_structure.dart';
 import '../universe/selector.dart';
@@ -413,25 +414,30 @@
   static const String tag = 'special-member-definition';
 
   @override
-  final ir.TreeNode node;
+  ir.TreeNode get node => _node.loaded();
+  final Deferrable<ir.TreeNode> _node;
   @override
   final MemberKind kind;
 
-  SpecialMemberDefinition(this.node, this.kind);
+  SpecialMemberDefinition(ir.TreeNode node, this.kind)
+      : _node = Deferrable.eager(node);
+
+  SpecialMemberDefinition._deserialized(this._node, this.kind);
 
   factory SpecialMemberDefinition.readFromDataSource(
       DataSourceReader source, MemberKind kind) {
     source.begin(tag);
-    ir.TreeNode node = source.readTreeNode();
+    Deferrable<ir.TreeNode> node =
+        source.readDeferrable(() => source.readTreeNode());
     source.end(tag);
-    return SpecialMemberDefinition(node, kind);
+    return SpecialMemberDefinition._deserialized(node, kind);
   }
 
   @override
   void writeToDataSink(DataSinkWriter sink) {
     sink.writeEnum(kind);
     sink.begin(tag);
-    sink.writeTreeNode(node);
+    sink.writeDeferrable(() => sink.writeTreeNode(node));
     sink.end(tag);
   }
 
diff --git a/pkg/compiler/lib/src/js_model/element_map_impl.dart b/pkg/compiler/lib/src/js_model/element_map_impl.dart
index c794980..149eb78 100644
--- a/pkg/compiler/lib/src/js_model/element_map_impl.dart
+++ b/pkg/compiler/lib/src/js_model/element_map_impl.dart
@@ -394,6 +394,8 @@
 
     source.begin(typeVariableDataTag);
     entityLookup.forEachTypeVariable((int index, JTypeVariable typeVariable) {
+      // TODO(natebiggs): Defer reading these type variables as they trigger
+      //   loading of some method bodies in the Kernel AST.
       JTypeVariableData data = JTypeVariableData.readFromDataSource(source);
       typeVariableMap[data.node] =
           typeVariables.registerByIndex(index, typeVariable, data);
diff --git a/pkg/compiler/lib/src/js_model/env.dart b/pkg/compiler/lib/src/js_model/env.dart
index b2a9bd3..0bf7e80 100644
--- a/pkg/compiler/lib/src/js_model/env.dart
+++ b/pkg/compiler/lib/src/js_model/env.dart
@@ -17,6 +17,7 @@
 import '../ir/util.dart';
 import '../js_model/class_type_variable_access.dart';
 import '../ordered_typeset.dart';
+import '../serialization/deferrable.dart';
 import '../serialization/serialization.dart';
 import 'closure.dart';
 import 'element_map.dart';
@@ -574,9 +575,13 @@
   final MemberDefinition definition;
 
   @override
-  final StaticTypeCache staticTypes;
+  StaticTypeCache get staticTypes => _staticTypes.loaded();
+  final Deferrable<StaticTypeCache> _staticTypes;
 
-  JMemberDataImpl(this.node, this.definition, this.staticTypes);
+  JMemberDataImpl(this.node, this.definition, StaticTypeCache staticTypes)
+      : _staticTypes = Deferrable.eager(staticTypes);
+
+  JMemberDataImpl._deserialized(this.node, this.definition, this._staticTypes);
 
   @override
   InterfaceType getMemberThisType(JsToElementMap elementMap) {
@@ -691,6 +696,10 @@
       MemberDefinition definition, StaticTypeCache staticTypes)
       : super(node, definition, staticTypes);
 
+  FunctionDataImpl._deserialized(ir.Member node, this.functionNode,
+      MemberDefinition definition, Deferrable<StaticTypeCache> staticTypes)
+      : super._deserialized(node, definition, staticTypes);
+
   factory FunctionDataImpl.readFromDataSource(DataSourceReader source) {
     source.begin(tag);
     ir.Member node = source.readMemberNode();
@@ -704,10 +713,11 @@
           "Unexpected member node $node (${node.runtimeType}).");
     }
     MemberDefinition definition = MemberDefinition.readFromDataSource(source);
-    StaticTypeCache staticTypes =
-        StaticTypeCache.readFromDataSource(source, node);
+    Deferrable<StaticTypeCache> staticTypes = source
+        .readDeferrable(() => StaticTypeCache.readFromDataSource(source, node));
     source.end(tag);
-    return FunctionDataImpl(node, functionNode, definition, staticTypes);
+    return FunctionDataImpl._deserialized(
+        node, functionNode, definition, staticTypes);
   }
 
   @override
@@ -716,7 +726,7 @@
     sink.begin(tag);
     sink.writeMemberNode(node);
     definition.writeToDataSink(sink);
-    staticTypes.writeToDataSink(sink, node);
+    sink.writeDeferrable(() => staticTypes.writeToDataSink(sink, node));
     sink.end(tag);
   }
 
@@ -745,21 +755,27 @@
   final InterfaceType memberThisType;
   @override
   final ClassTypeVariableAccess classTypeVariableAccess;
-  final List<ir.TypeParameter> typeParameters;
+  List<ir.TypeParameter> get typeParameters => _typeParameters.loaded();
+  final Deferrable<List<ir.TypeParameter>> _typeParameters;
 
   SignatureFunctionData(this.definition, this.memberThisType,
-      this.typeParameters, this.classTypeVariableAccess);
+      List<ir.TypeParameter> typeParameters, this.classTypeVariableAccess)
+      : _typeParameters = Deferrable.eager(typeParameters);
+
+  SignatureFunctionData._deserialized(this.definition, this.memberThisType,
+      this._typeParameters, this.classTypeVariableAccess);
 
   factory SignatureFunctionData.readFromDataSource(DataSourceReader source) {
     source.begin(tag);
     MemberDefinition definition = MemberDefinition.readFromDataSource(source);
     InterfaceType /*?*/ memberThisType =
         source.readDartTypeOrNull() as InterfaceType /*?*/;
-    List<ir.TypeParameter> typeParameters = source.readTypeParameterNodes();
+    Deferrable<List<ir.TypeParameter>> typeParameters =
+        source.readDeferrable(() => source.readTypeParameterNodes());
     ClassTypeVariableAccess classTypeVariableAccess =
         source.readEnum(ClassTypeVariableAccess.values);
     source.end(tag);
-    return SignatureFunctionData(
+    return SignatureFunctionData._deserialized(
         definition, memberThisType, typeParameters, classTypeVariableAccess);
   }
 
@@ -769,7 +785,7 @@
     sink.begin(tag);
     definition.writeToDataSink(sink);
     sink.writeDartTypeOrNull(memberThisType);
-    sink.writeTypeParameterNodes(typeParameters);
+    sink.writeDeferrable(() => sink.writeTypeParameterNodes(typeParameters));
     sink.writeEnum(classTypeVariableAccess);
     sink.end(tag);
   }
@@ -891,6 +907,13 @@
       MemberDefinition definition, StaticTypeCache staticTypes)
       : super(node, functionNode, definition, staticTypes);
 
+  JConstructorDataImpl._deserialized(
+      ir.Member node,
+      ir.FunctionNode functionNode,
+      MemberDefinition definition,
+      Deferrable<StaticTypeCache> staticTypes)
+      : super._deserialized(node, functionNode, definition, staticTypes);
+
   factory JConstructorDataImpl.readFromDataSource(DataSourceReader source) {
     source.begin(tag);
     ir.Member node = source.readMemberNode();
@@ -904,10 +927,11 @@
           "Unexpected member node $node (${node.runtimeType}).");
     }
     MemberDefinition definition = MemberDefinition.readFromDataSource(source);
-    StaticTypeCache staticTypes =
-        StaticTypeCache.readFromDataSource(source, node);
+    Deferrable<StaticTypeCache> staticTypes = source
+        .readDeferrable(() => StaticTypeCache.readFromDataSource(source, node));
     source.end(tag);
-    return JConstructorDataImpl(node, functionNode, definition, staticTypes);
+    return JConstructorDataImpl._deserialized(
+        node, functionNode, definition, staticTypes);
   }
 
   @override
@@ -917,7 +941,7 @@
     sink.writeMemberNode(node);
     definition.writeToDataSink(sink);
     assert(constructorBody == null);
-    staticTypes.writeToDataSink(sink, node);
+    sink.writeDeferrable(() => staticTypes.writeToDataSink(sink, node));
     sink.end(tag);
   }
 
@@ -935,6 +959,13 @@
       MemberDefinition definition, StaticTypeCache staticTypes)
       : super(node, functionNode, definition, staticTypes);
 
+  ConstructorBodyDataImpl._deserialized(
+      ir.Member node,
+      ir.FunctionNode functionNode,
+      MemberDefinition definition,
+      Deferrable<StaticTypeCache> staticTypes)
+      : super._deserialized(node, functionNode, definition, staticTypes);
+
   factory ConstructorBodyDataImpl.readFromDataSource(DataSourceReader source) {
     source.begin(tag);
     ir.Member node = source.readMemberNode();
@@ -948,10 +979,11 @@
           "Unexpected member node $node (${node.runtimeType}).");
     }
     MemberDefinition definition = MemberDefinition.readFromDataSource(source);
-    StaticTypeCache staticTypes =
-        StaticTypeCache.readFromDataSource(source, node);
+    Deferrable<StaticTypeCache> staticTypes = source
+        .readDeferrable(() => StaticTypeCache.readFromDataSource(source, node));
     source.end(tag);
-    return ConstructorBodyDataImpl(node, functionNode, definition, staticTypes);
+    return ConstructorBodyDataImpl._deserialized(
+        node, functionNode, definition, staticTypes);
   }
 
   @override
@@ -960,7 +992,7 @@
     sink.begin(tag);
     sink.writeMemberNode(node);
     definition.writeToDataSink(sink);
-    staticTypes.writeToDataSink(sink, node);
+    sink.writeDeferrable(() => staticTypes.writeToDataSink(sink, node));
     sink.end(tag);
   }
 
@@ -986,14 +1018,18 @@
       ir.Field node, MemberDefinition definition, StaticTypeCache staticTypes)
       : super(node, definition, staticTypes);
 
+  JFieldDataImpl._deserialized(ir.Field node, MemberDefinition definition,
+      Deferrable<StaticTypeCache> staticTypes)
+      : super._deserialized(node, definition, staticTypes);
+
   factory JFieldDataImpl.readFromDataSource(DataSourceReader source) {
     source.begin(tag);
     ir.Member node = source.readMemberNode();
     MemberDefinition definition = MemberDefinition.readFromDataSource(source);
-    StaticTypeCache staticTypes =
-        StaticTypeCache.readFromDataSource(source, node);
+    Deferrable<StaticTypeCache> staticTypes = source
+        .readDeferrable(() => StaticTypeCache.readFromDataSource(source, node));
     source.end(tag);
-    return JFieldDataImpl(node, definition, staticTypes);
+    return JFieldDataImpl._deserialized(node, definition, staticTypes);
   }
 
   @override
@@ -1002,7 +1038,7 @@
     sink.begin(tag);
     sink.writeMemberNode(node);
     definition.writeToDataSink(sink);
-    staticTypes.writeToDataSink(sink, node);
+    sink.writeDeferrable(() => staticTypes.writeToDataSink(sink, node));
     sink.end(tag);
   }
 
diff --git a/pkg/compiler/lib/src/js_model/jrecord_field_interface.dart b/pkg/compiler/lib/src/js_model/jrecord_field_interface.dart
new file mode 100644
index 0000000..17f3bd0
--- /dev/null
+++ b/pkg/compiler/lib/src/js_model/jrecord_field_interface.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// TODO(48820): delete this class once migration is complete.
+abstract class JRecordFieldInterface {}
diff --git a/pkg/compiler/lib/src/js_model/locals.dart b/pkg/compiler/lib/src/js_model/locals.dart
index f76178a..7cdd82c 100644
--- a/pkg/compiler/lib/src/js_model/locals.dart
+++ b/pkg/compiler/lib/src/js_model/locals.dart
@@ -14,6 +14,7 @@
 import '../elements/indexed.dart';
 import '../elements/jumps.dart';
 import '../elements/types.dart';
+import '../serialization/deferrable.dart';
 import '../serialization/serialization.dart';
 
 import 'element_map.dart';
@@ -43,18 +44,19 @@
       MemberEntity Function(MemberEntity) localMapKeyLookup,
       DataSourceReader source) {
     source.begin(tag);
-    Map<MemberEntity, KernelToLocalsMap> _localsMaps = {};
+    Map<MemberEntity, Deferrable<KernelToLocalsMap>> _localsMaps = {};
     int mapCount = source.readInt();
     for (int i = 0; i < mapCount; i++) {
-      KernelToLocalsMap localsMap =
-          KernelToLocalsMapImpl.readFromDataSource(source);
+      Deferrable<KernelToLocalsMap> localsMap = source.readDeferrable(
+          () => KernelToLocalsMapImpl.readFromDataSource(source));
       List<MemberEntity> members = source.readMembers();
       for (MemberEntity member in members) {
         _localsMaps[member] = localsMap;
       }
     }
     source.end(tag);
-    return GlobalLocalsMap.internal(localMapKeyLookup, _localsMaps);
+    return GlobalLocalsMap.internal(
+        localMapKeyLookup, DeferrableValueMap(_localsMaps));
   }
 
   /// Serializes this [GlobalLocalsMap] to [sink].
@@ -72,7 +74,7 @@
     sink.writeInt(reverseMap.length);
     reverseMap
         .forEach((KernelToLocalsMap localsMap, List<MemberEntity> members) {
-      localsMap.writeToDataSink(sink);
+      sink.writeDeferrable(() => localsMap.writeToDataSink(sink));
       sink.writeMembers(members);
     });
     sink.end(tag);
diff --git a/pkg/compiler/lib/src/native/behavior.dart b/pkg/compiler/lib/src/native/behavior.dart
index 3c1e721..563f4a5 100644
--- a/pkg/compiler/lib/src/native/behavior.dart
+++ b/pkg/compiler/lib/src/native/behavior.dart
@@ -2,8 +2,6 @@
 // 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.10
-
 import '../common.dart';
 import '../common/elements.dart' show CommonElements, ElementEnvironment;
 import '../constants/values.dart';
@@ -20,7 +18,7 @@
 import 'native_throw_behavior.dart';
 export 'native_throw_behavior.dart';
 
-typedef TypeLookup = Object /*DartType|SpecialType*/
+typedef TypeLookup = Object? /*DartType|SpecialType*/
     Function(String typeString, {bool required});
 
 /// This class is a temporary work-around until we get a more powerful DartType.
@@ -87,10 +85,10 @@
   /// element.
   final List<Object> typesInstantiated = [];
 
-  String codeTemplateText;
+  String? codeTemplateText;
   // If this behavior is for a JS expression, [codeTemplate] contains the
   // parsed tree.
-  js.Template codeTemplate;
+  js.Template? codeTemplate;
 
   final SideEffects sideEffects;
 
@@ -128,7 +126,7 @@
 
     List<Object> typesReturned = readTypes();
     List<Object> typesInstantiated = readTypes();
-    String codeTemplateText = source.readStringOrNull();
+    String? codeTemplateText = source.readStringOrNull();
     SideEffects sideEffects = SideEffects.readFromDataSource(source);
     int throwBehavior = source.readInt();
     bool isAllocation = source.readBool();
@@ -160,7 +158,7 @@
         if (type is DartType) {
           dartTypes.add(type);
         } else {
-          specialTypes.add(type);
+          specialTypes.add(type as SpecialType);
         }
       }
       sink.writeDartTypes(dartTypes);
@@ -284,14 +282,14 @@
   /// [validTags] can be used to restrict which tags are accepted.
   static void processSpecString(DartTypes dartTypes,
       DiagnosticReporter reporter, Spannable spannable, String specString,
-      {Iterable<String> validTags,
-      void setSideEffects(SideEffects newEffects),
-      void setThrows(NativeThrowBehavior throwKind),
-      void setIsAllocation(bool isAllocation),
-      void setUseGvn(bool useGvn),
-      TypeLookup lookupType,
-      List<Object> typesReturned,
-      List<Object> typesInstantiated,
+      {Iterable<String>? validTags,
+      required void setSideEffects(SideEffects newEffects),
+      void Function(NativeThrowBehavior)? setThrows,
+      void Function(bool)? setIsAllocation,
+      void Function(bool)? setUseGvn,
+      required TypeLookup lookupType,
+      required List<Object> typesReturned,
+      required List<Object> typesInstantiated,
       objectType,
       nullType}) {
     bool seenError = false;
@@ -317,7 +315,9 @@
     /// *  '' or 'var' - in which case [onVar] is called,
     /// *  'T1|...|Tn' - in which case [onType] is called for each resolved Ti.
     void resolveTypesString(DartTypes dartTypes, String typesString,
-        {onVoid(), onVar(), onType(type)}) {
+        {void Function()? onVoid,
+        void Function()? onVar,
+        required void Function(Object) onType}) {
       // Various things that are not in fact types.
       if (typesString == 'void') {
         if (onVoid != null) {
@@ -382,11 +382,8 @@
     }
 
     // Enum-like tags are looked up in a map. True signature is:
-    //
-    //  T tagValueLookup<T>(String tag, Map<String, T> map);
-    //
-    dynamic tagValueLookup(String tag, Map<String, dynamic> map) {
-      String tagString = values[tag];
+    T? tagValueLookup<T>(String tag, Map<String, T> map) {
+      String? tagString = values[tag];
       if (tagString == null) return null;
       var value = map[tagString];
       if (value == null) {
@@ -395,7 +392,7 @@
       return value;
     }
 
-    String returns = values['returns'];
+    String? returns = values['returns'];
     if (returns != null) {
       resolveTypesString(dartTypes, returns, onVar: () {
         typesReturned.add(objectType);
@@ -405,7 +402,7 @@
       });
     }
 
-    String creates = values['creates'];
+    String? creates = values['creates'];
     if (creates != null) {
       resolveTypesString(dartTypes, creates, onVoid: () {
         reportError("Invalid type string 'creates:$creates'");
@@ -427,11 +424,11 @@
 
     const boolOptions = <String, bool>{'true': true, 'false': false};
 
-    SideEffects sideEffects =
+    SideEffects? sideEffects =
         processEffects(reportError, values['effects'], values['depends']);
-    NativeThrowBehavior throwsKind = tagValueLookup('throws', throwsOption);
-    bool isAllocation = tagValueLookup('new', boolOptions);
-    bool useGvn = tagValueLookup('gvn', boolOptions);
+    NativeThrowBehavior? throwsKind = tagValueLookup('throws', throwsOption);
+    bool? isAllocation = tagValueLookup('new', boolOptions);
+    bool? useGvn = tagValueLookup('gvn', boolOptions);
 
     if (isAllocation == true && useGvn == true) {
       reportError("'new' and 'gvn' are incompatible");
@@ -442,13 +439,31 @@
     // TODO(sra): Simplify [throwBehavior] using [sideEffects].
 
     if (sideEffects != null) setSideEffects(sideEffects);
-    if (throwsKind != null) setThrows(throwsKind);
-    if (isAllocation != null) setIsAllocation(isAllocation);
-    if (useGvn != null) setUseGvn(useGvn);
+    if (throwsKind != null) {
+      if (setThrows == null) {
+        reportError("'throws' not allowed here");
+      } else {
+        setThrows(throwsKind);
+      }
+    }
+    if (isAllocation != null) {
+      if (setIsAllocation == null) {
+        reportError("'allocation' not allowed here");
+      } else {
+        setIsAllocation(isAllocation);
+      }
+    }
+    if (useGvn != null) {
+      if (setUseGvn == null) {
+        reportError("'gvn' not allowed here");
+      } else {
+        setUseGvn(useGvn);
+      }
+    }
   }
 
-  static SideEffects processEffects(
-      void reportError(String message), String effects, String depends) {
+  static SideEffects? processEffects(
+      void reportError(String message), String? effects, String? depends) {
     if (effects == null && depends == null) return null;
 
     if (effects == null || depends == null) {
@@ -530,7 +545,7 @@
     NativeBehavior behavior = NativeBehavior();
 
     behavior.codeTemplateText = codeString;
-    behavior.codeTemplate = js.js.parseForeignJS(behavior.codeTemplateText);
+    behavior.codeTemplate = js.js.parseForeignJS(codeString);
 
     bool sideEffectsAreEncodedInSpecString = false;
 
@@ -565,11 +580,12 @@
         nullType: commonElements.nullType);
 
     if (!sideEffectsAreEncodedInSpecString) {
-      SideEffectsVisitor(behavior.sideEffects).visit(behavior.codeTemplate.ast);
+      SideEffectsVisitor(behavior.sideEffects)
+          .visit(behavior.codeTemplate!.ast);
     }
     if (!throwBehaviorFromSpecString) {
       behavior.throwBehavior =
-          ThrowBehaviorVisitor().analyze(behavior.codeTemplate.ast);
+          ThrowBehaviorVisitor().analyze(behavior.codeTemplate!.ast);
     }
 
     return behavior;
@@ -582,7 +598,7 @@
       TypeLookup lookupType,
       DiagnosticReporter reporter,
       CommonElements commonElements,
-      {List<String> validTags}) {
+      {List<String>? validTags}) {
     void setSideEffects(SideEffects newEffects) {
       behavior.sideEffects.setTo(newEffects);
     }
@@ -628,7 +644,7 @@
     return behavior;
   }
 
-  static dynamic /*DartType|SpecialType*/ _parseType(
+  static Object /*DartType|SpecialType*/ _parseType(
       DartTypes dartTypes, String typeString, TypeLookup lookupType) {
     if (typeString == '=Object') return SpecialType.JsObject;
     if (typeString == 'dynamic') {
@@ -685,7 +701,7 @@
   final CompilerOptions options;
   DartTypes get dartTypes => commonElements.dartTypes;
 
-  NativeBehavior _behavior;
+  late NativeBehavior _behavior;
 
   BehaviorBuilder(this.elementEnvironment, this.commonElements,
       this.nativeBasicData, this.reporter, this.options);
@@ -694,8 +710,8 @@
       Iterable<String> returnsAnnotations, TypeLookup lookupType) {
     if (createsAnnotations.isEmpty && returnsAnnotations.isEmpty) return;
 
-    List<Object> creates = _collect(createsAnnotations, lookupType);
-    List<Object> returns = _collect(returnsAnnotations, lookupType);
+    List<Object>? creates = _collect(createsAnnotations, lookupType);
+    List<Object>? returns = _collect(returnsAnnotations, lookupType);
 
     if (creates != null) {
       _behavior.typesInstantiated
@@ -712,14 +728,13 @@
   /// Returns a list of type constraints from the annotations of
   /// [annotationClass].
   /// Returns `null` if no constraints.
-  List<dynamic> _collect(Iterable<String> annotations, TypeLookup lookupType) {
-    List<dynamic> types = null;
+  List<Object>? _collect(Iterable<String> annotations, TypeLookup lookupType) {
+    List<Object>? types = null;
     for (String specString in annotations) {
       for (final typeString in specString.split('|')) {
         var type = NativeBehavior._parseType(
             commonElements.dartTypes, typeString, lookupType);
-        if (types == null) types = [];
-        types.add(type);
+        (types ??= []).add(type);
       }
     }
     return types;
@@ -804,7 +819,7 @@
       Iterable<String> createsAnnotations,
       Iterable<String> returnsAnnotations,
       TypeLookup lookupType,
-      {bool isJsInterop}) {
+      {required bool isJsInterop}) {
     _behavior = NativeBehavior();
     // TODO(sigmund,sra): consider doing something better for numeric types.
     _addReturnType(!isJsInterop ? type : commonElements.dynamicType);
@@ -829,7 +844,7 @@
       Iterable<String> createAnnotations,
       Iterable<String> returnsAnnotations,
       TypeLookup lookupType,
-      {bool isJsInterop}) {
+      {required bool isJsInterop}) {
     _behavior = NativeBehavior();
     DartType returnType = type.returnType;
     // Note: For dart:html and other internal libraries we maintain, we can
diff --git a/pkg/compiler/lib/src/serialization/binary_sink.dart b/pkg/compiler/lib/src/serialization/binary_sink.dart
index c408ccc..01e6d89 100644
--- a/pkg/compiler/lib/src/serialization/binary_sink.dart
+++ b/pkg/compiler/lib/src/serialization/binary_sink.dart
@@ -13,6 +13,7 @@
   final Sink<List<int>> sink;
   // Nullable and non-final to allow storage to be released.
   BufferedSink? _bufferedSink;
+  final Map<int, int> _deferredOffsetToSize = {};
   int _length = 0;
 
   BinaryDataSink(this.sink) : _bufferedSink = BufferedSink(sink);
@@ -54,6 +55,21 @@
     }
   }
 
+  void _writeUInt32(int value) {
+    _length += 4;
+    _bufferedSink!.addByte4((value >> 24) & 0xFF, (value >> 16) & 0xFF,
+        (value >> 8) & 0xFF, value & 0xFF);
+  }
+
+  @override
+  void writeDeferred(void writer()) {
+    final indexOffset = _length;
+    writeInt(0); // Padding so the offset won't collide with a nested write.
+    final dataStartOffset = _length;
+    writer();
+    _deferredOffsetToSize[indexOffset] = _length - dataStartOffset;
+  }
+
   @override
   void writeEnum(dynamic value) {
     writeInt(value.index);
@@ -61,8 +77,16 @@
 
   @override
   void close() {
+    final deferredDataStart = _length;
+    writeInt(_deferredOffsetToSize.length);
+    for (final entry in _deferredOffsetToSize.entries) {
+      writeInt(entry.key);
+      writeInt(entry.value);
+    }
+    _writeUInt32(deferredDataStart);
     _bufferedSink!.flushAndDestroy();
     _bufferedSink = null;
+    _deferredOffsetToSize.clear();
     sink.close();
   }
 }
diff --git a/pkg/compiler/lib/src/serialization/binary_source.dart b/pkg/compiler/lib/src/serialization/binary_source.dart
index 12ecd79..3d8ba61 100644
--- a/pkg/compiler/lib/src/serialization/binary_source.dart
+++ b/pkg/compiler/lib/src/serialization/binary_source.dart
@@ -14,10 +14,22 @@
   int _byteOffset = 0;
   final List<int> _bytes;
   final StringInterner? _stringInterner;
+  late final Map<int, int> _deferredOffsetToSize;
 
   BinaryDataSource(this._bytes, {StringInterner? stringInterner})
       : _stringInterner = stringInterner {
     assert((_bytes as dynamic) != null); // TODO(48820): Remove when sound.
+    final deferredDataStart = readAtOffset(_bytes.length - 4, _readUint32);
+    _deferredOffsetToSize = readAtOffset(deferredDataStart, () {
+      final deferredSizesCount = readInt();
+      final result = <int, int>{};
+      for (var i = 0; i < deferredSizesCount; i++) {
+        final offset = readInt();
+        final size = readInt();
+        result[offset] = size;
+      }
+      return result;
+    });
   }
 
   @override
@@ -76,6 +88,29 @@
     return value;
   }
 
+  int _readUint32() {
+    return (_readByte() << 24) |
+        (_readByte() << 16) |
+        (_readByte() << 8) |
+        _readByte();
+  }
+
+  @override
+  int readDeferred() {
+    final indexOffset = _byteOffset;
+    readInt(); // Read collision padding.
+    final dataOffset = _byteOffset;
+    final dataLength = _deferredOffsetToSize[indexOffset]!;
+    _byteOffset += dataLength;
+    return dataOffset;
+  }
+
+  @override
+  E readDeferredAsEager<E>(E reader()) {
+    readInt(); // Read collision padding.
+    return reader();
+  }
+
   @override
   int get length => _bytes.length;
 
diff --git a/pkg/compiler/lib/src/serialization/data_sink.dart b/pkg/compiler/lib/src/serialization/data_sink.dart
index b87ebcd..ef8cc8b 100644
--- a/pkg/compiler/lib/src/serialization/data_sink.dart
+++ b/pkg/compiler/lib/src/serialization/data_sink.dart
@@ -25,6 +25,10 @@
   /// Serialization of a section end tag. May be omitted by some writers.
   void endTag(String tag);
 
+  /// Writes a deferred entity which can be skipped when reading and read later
+  /// via an offset read.
+  void writeDeferred(void writer());
+
   /// Closes any underlying data sinks.
   void close();
 }
diff --git a/pkg/compiler/lib/src/serialization/data_source.dart b/pkg/compiler/lib/src/serialization/data_source.dart
index 9ebc18b..dec8bab 100644
--- a/pkg/compiler/lib/src/serialization/data_source.dart
+++ b/pkg/compiler/lib/src/serialization/data_source.dart
@@ -22,8 +22,15 @@
   /// Deserialization of an enum value in [values].
   E readEnum<E>(List<E> values);
 
+  /// Returns the offset for a deferred entity and skips it in the read queue.
+  /// The offset can later be passed to [readAtOffset] to get the value.
+  int readDeferred();
+
+  /// Eagerly reads and returns the value for a deferred entity.
+  E readDeferredAsEager<E>(E reader());
+
   /// Calls [reader] to read a value at the provided offset in the underlying
-  /// data stream.
+  /// data stream. Use with [readDeferred] to read a deferred value.
   E readAtOffset<E>(int offset, E reader());
 
   /// The length of the underlying data source.
diff --git a/pkg/compiler/lib/src/serialization/deferrable.dart b/pkg/compiler/lib/src/serialization/deferrable.dart
new file mode 100644
index 0000000..9b84e93
--- /dev/null
+++ b/pkg/compiler/lib/src/serialization/deferrable.dart
@@ -0,0 +1,146 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'package:compiler/src/serialization/serialization_interfaces.dart';
+
+/// Interface for data that may be deserialized lazily.
+///
+/// This interface should be used to wrap data objects that aren't needed in
+/// later phases of the compiler. Usage of this class should follow a set
+/// pattern. Given a class `C` with a field `m0` of type `E` that we wish to
+/// make deferrable:
+///
+/// 1) `m0` should be replaced with an internal field, `_m1`, of type
+///  `Deferrable<E>`.
+/// 2) An internal constructor should be added to `C` that takes a `d` of type
+///  `Deferrable<E>` to initialize `_m1`. This internal constructor should be
+///  called from the readFromSource method/factory to create the instance
+///  of `C`. `d` should be obtained using [DataSourceReader.readDeferrable].
+/// 3) Any existing constructors of `C` should maintain the same signature
+///  and initialize `_m1` passing `m` to [Deferrable.eager] where m is the value
+///  previously used to initialize `m0`.
+/// 4) If there are external references to `m0` then `C`s interface should be
+///  maintained. A getter `m0` should be added: `E get m0 => _m1.loaded()`
+/// 5) If all references to `m0` were internal, they can simply be replaced
+///  with calls to `_m1.loaded()`.
+///
+/// Example class before:
+///
+/// class Foo {
+///   final Bar bar;
+///
+///   Foo(this.bar);
+///
+///   factory Foo.readFromSource(DataSourceReader reader) {
+///     return Foo(Bar.readFromSource(reader));
+///   }
+/// }
+///
+/// After:
+///
+/// class Foo {
+///   Bar get bar => _bar.loaded();
+///   final Deferrable<Bar> _bar;
+///
+///   Foo(Bar bar) : _bar = Deferrable.eager(bar);
+///   Foo._deserialized(this._bar);
+///
+///   factory Foo.readFromSource(DataSourceReader reader) {
+///     return Foo._deserialized(
+///         reader.readDeferrable(() => Bar.readFromSource(reader)));
+///   }
+/// }
+abstract class Deferrable<E> {
+  E loaded();
+  static int count = 0;
+
+  factory Deferrable.deferred(DataSourceReader reader, E f(), int offset,
+          {bool cacheData = true}) =>
+      cacheData
+          ? _DeferredCache(reader, f, offset)
+          : _Deferred(reader, f, offset);
+  const factory Deferrable.eager(E data) = _Eager;
+
+  const Deferrable();
+}
+
+class _Eager<E> extends Deferrable<E> {
+  final E _data;
+  @override
+  E loaded() => _data;
+  const _Eager(this._data);
+}
+
+class _Deferred<E> extends Deferrable<E> {
+  final DataSourceReader _reader;
+  final E Function() _dataLoader;
+  final int _dataOffset;
+  @override
+  E loaded() => _reader.readWithOffset(_dataOffset, _dataLoader);
+  _Deferred(this._reader, this._dataLoader, this._dataOffset);
+}
+
+class _DeferredCache<E> extends Deferrable<E> {
+  final int _dataOffset;
+  // Below fields are nullable so they can be cleared after loading.
+  DataSourceReader? _reader;
+  E Function()? _dataLoader;
+  late final E _data = _loadData();
+
+  @override
+  E loaded() => _data;
+
+  E _loadData() {
+    final reader = _reader!;
+    final dataLoader = _dataLoader!;
+    _reader = null;
+    _dataLoader = null;
+    return reader.readWithOffset(_dataOffset, dataLoader);
+  }
+
+  _DeferredCache(this._reader, this._dataLoader, this._dataOffset);
+}
+
+/// Implementation of [Map] in which each value of type [V] is internally
+/// [Deferrable].
+///
+/// This map should be used when values of type [V] are expensive to
+/// deserialize. This abstracts away the laziness allowing the deferred map to
+/// be used as if the values were not deferred.
+///
+/// The provided map can have a mix of eager and lazy [Deferrable]s if
+/// there are a mix of expensive and cheap values.
+class DeferrableValueMap<K, V> with MapMixin<K, V> {
+  DeferrableValueMap(this._map);
+  final Map<K, Deferrable<V>> _map;
+  @override
+  V? operator [](Object? key) {
+    return _map[key]?.loaded();
+  }
+
+  @override
+  void operator []=(K key, V value) {
+    _map[key] = Deferrable.eager(value);
+  }
+
+  @override
+  void clear() {
+    _map.clear();
+  }
+
+  @override
+  Iterable<K> get keys => _map.keys;
+
+  @override
+  V? remove(Object? key) {
+    return _map.remove(key)?.loaded();
+  }
+
+  @override
+  V putIfAbsent(K key, V ifAbsent()) {
+    return _map.putIfAbsent(key, () => Deferrable.eager(ifAbsent())).loaded();
+  }
+}
diff --git a/pkg/compiler/lib/src/serialization/object_sink.dart b/pkg/compiler/lib/src/serialization/object_sink.dart
index 3bbbd3b..8e8c984 100644
--- a/pkg/compiler/lib/src/serialization/object_sink.dart
+++ b/pkg/compiler/lib/src/serialization/object_sink.dart
@@ -44,6 +44,17 @@
   }
 
   @override
+  void writeDeferred(void writer()) {
+    assert((writer as dynamic) != null); // TODO(48820): Remove when sound.
+    final sizeIndex = length;
+    writeInt(0); // placeholder
+    final startIndex = length;
+    writer();
+    final endIndex = length;
+    _data![sizeIndex] = endIndex - startIndex;
+  }
+
+  @override
   void close() {
     _data = null;
   }
diff --git a/pkg/compiler/lib/src/serialization/object_source.dart b/pkg/compiler/lib/src/serialization/object_source.dart
index 0a7ca9d..6ea211a 100644
--- a/pkg/compiler/lib/src/serialization/object_source.dart
+++ b/pkg/compiler/lib/src/serialization/object_source.dart
@@ -60,6 +60,19 @@
   }
 
   @override
+  int readDeferred() {
+    final dataOffset = _index;
+    _index += readInt();
+    return dataOffset;
+  }
+
+  @override
+  E readDeferredAsEager<E>(E reader()) {
+    readInt(); // Read and throw away the length.
+    return reader();
+  }
+
+  @override
   int get length => _data.length;
 
   @override
diff --git a/pkg/compiler/lib/src/serialization/serialization.dart b/pkg/compiler/lib/src/serialization/serialization.dart
index 7300d00..e15e038 100644
--- a/pkg/compiler/lib/src/serialization/serialization.dart
+++ b/pkg/compiler/lib/src/serialization/serialization.dart
@@ -26,6 +26,7 @@
 import '../options.dart';
 import 'data_sink.dart';
 import 'data_source.dart';
+import 'deferrable.dart';
 import 'member_data.dart';
 import 'serialization_interfaces.dart' as migrated
     show DataSourceReader, DataSinkWriter;
diff --git a/pkg/compiler/lib/src/serialization/serialization_interfaces.dart b/pkg/compiler/lib/src/serialization/serialization_interfaces.dart
index 8a4ec2b..dfc601b 100644
--- a/pkg/compiler/lib/src/serialization/serialization_interfaces.dart
+++ b/pkg/compiler/lib/src/serialization/serialization_interfaces.dart
@@ -9,6 +9,7 @@
 import '../elements/types.dart' show DartType;
 import '../inferrer/abstract_value_domain.dart' show AbstractValue;
 
+import 'deferrable.dart';
 export 'tags.dart';
 
 abstract class StringInterner {
@@ -113,6 +114,8 @@
       {bool allowNull = false});
 
   void writeAbstractValue(AbstractValue value);
+
+  void writeDeferrable(void f());
 }
 
 /// Migrated interface for methods of DataSourceReader.
@@ -192,4 +195,5 @@
 
   E readWithSource<E>(DataSourceReader source, E f());
   E readWithOffset<E>(int offset, E f());
+  Deferrable<E> readDeferrable<E>(E f(), {bool cacheData = true});
 }
diff --git a/pkg/compiler/lib/src/serialization/sink.dart b/pkg/compiler/lib/src/serialization/sink.dart
index c3d96ab..f1ae16d 100644
--- a/pkg/compiler/lib/src/serialization/sink.dart
+++ b/pkg/compiler/lib/src/serialization/sink.dart
@@ -137,6 +137,15 @@
     }
   }
 
+  @override
+  void writeDeferrable(void f()) {
+    if (enableDeferredStrategy) {
+      _sinkWriter.writeDeferred(f);
+    } else {
+      f();
+    }
+  }
+
   /// Writes a reference to [value] to this data sink. If [value] has not yet
   /// been serialized, [f] is called to serialize the value itself.
   @override
diff --git a/pkg/compiler/lib/src/serialization/source.dart b/pkg/compiler/lib/src/serialization/source.dart
index 287a402..c512786 100644
--- a/pkg/compiler/lib/src/serialization/source.dart
+++ b/pkg/compiler/lib/src/serialization/source.dart
@@ -20,6 +20,7 @@
       List<ir.DartType>.empty();
 
   final bool enableDeferredStrategy;
+  final bool useDeferredStrategy;
   final bool useDataKinds;
   final ValueInterner /*?*/ interner;
   DataSourceIndices importedIndices;
@@ -96,7 +97,10 @@
   }
 
   DataSourceReader(this._sourceReader, CompilerOptions options,
-      {this.useDataKinds = false, this.importedIndices, this.interner})
+      {this.useDataKinds = false,
+      this.importedIndices,
+      this.interner,
+      this.useDeferredStrategy = false})
       : enableDeferredStrategy =
             (options?.features?.deferredSerialization?.isEnabled ?? false),
         endOffset = (importedIndices?.previousSourceReader?.endOffset ?? 0) +
@@ -228,6 +232,16 @@
     return _sourceReader.readAtOffset(offset, f);
   }
 
+  @override
+  Deferrable<E> readDeferrable<E>(E f(), {bool cacheData = true}) {
+    return enableDeferredStrategy
+        ? (useDeferredStrategy
+            ? Deferrable<E>.deferred(this, f, _sourceReader.readDeferred(),
+                cacheData: cacheData)
+            : Deferrable<E>.eager(_sourceReader.readDeferredAsEager(f)))
+        : Deferrable<E>.eager(f());
+  }
+
   /// Invoke [f] in the context of [member]. This sets up support for
   /// deserialization of `ir.TreeNode`s using the `readTreeNode*InContext`
   /// methods.
diff --git a/pkg/compiler/lib/src/serialization/task.dart b/pkg/compiler/lib/src/serialization/task.dart
index 0635047..265359b 100644
--- a/pkg/compiler/lib/src/serialization/task.dart
+++ b/pkg/compiler/lib/src/serialization/task.dart
@@ -245,7 +245,8 @@
   Future<DataAndIndices<JsClosedWorld>> deserializeClosedWorld(
       Environment environment,
       AbstractValueStrategy abstractValueStrategy,
-      ir.Component component) async {
+      ir.Component component,
+      bool useDeferredSourceReads) async {
     return await measureIoSubtask('deserialize closed world', () async {
       _reporter.log('Reading data from ${_options.readClosedWorldUri}');
       api.Input<List<int>> dataInput = await _provider.readFromUri(
@@ -254,7 +255,8 @@
       DataSourceReader source = DataSourceReader(
           BinaryDataSource(dataInput.data, stringInterner: _stringInterner),
           _options,
-          interner: _valueInterner);
+          interner: _valueInterner,
+          useDeferredStrategy: useDeferredSourceReads);
       var closedWorld = deserializeClosedWorldFromSource(_options, _reporter,
           environment, abstractValueStrategy, component, source);
       return DataAndIndices(closedWorld, source.exportIndices());
@@ -283,7 +285,8 @@
           Environment environment,
           AbstractValueStrategy abstractValueStrategy,
           ir.Component component,
-          DataAndIndices<JsClosedWorld> closedWorldAndIndices) async {
+          DataAndIndices<JsClosedWorld> closedWorldAndIndices,
+          bool useDeferredSourceReads) async {
     return await measureIoSubtask('deserialize data', () async {
       _reporter.log('Reading data from ${_options.readDataUri}');
       api.Input<List<int>> dataInput = await _provider
@@ -292,7 +295,8 @@
           BinaryDataSource(dataInput.data, stringInterner: _stringInterner),
           _options,
           interner: _valueInterner,
-          importedIndices: closedWorldAndIndices.indices);
+          importedIndices: closedWorldAndIndices.indices,
+          useDeferredStrategy: useDeferredSourceReads);
       return DataAndIndices(
           deserializeGlobalTypeInferenceResultsFromSource(
               _options,
@@ -346,7 +350,8 @@
       JsBackendStrategy backendStrategy,
       GlobalTypeInferenceResults globalTypeInferenceResults,
       CodegenInputs codegenInputs,
-      DataSourceIndices indices) async {
+      DataSourceIndices indices,
+      bool useDeferredSourceReads) async {
     int shards = _options.codegenShards;
     JClosedWorld closedWorld = globalTypeInferenceResults.closedWorld;
     Map<MemberEntity, CodegenResult> results = {};
@@ -358,8 +363,8 @@
             await _provider.readFromUri(uri, inputKind: api.InputKind.binary);
         // TODO(36983): This code is extracted because there appeared to be a
         // memory leak for large buffer held by `source`.
-        _deserializeCodegenInput(
-            backendStrategy, closedWorld, uri, dataInput, indices, results);
+        _deserializeCodegenInput(backendStrategy, closedWorld, uri, dataInput,
+            indices, results, useDeferredSourceReads);
         dataInput.release();
       });
     }
@@ -373,12 +378,14 @@
       Uri uri,
       api.Input<List<int>> dataInput,
       DataSourceIndices importedIndices,
-      Map<MemberEntity, CodegenResult> results) {
+      Map<MemberEntity, CodegenResult> results,
+      bool useDeferredSourceReads) {
     DataSourceReader source = DataSourceReader(
         BinaryDataSource(dataInput.data, stringInterner: _stringInterner),
         _options,
         interner: _valueInterner,
-        importedIndices: importedIndices);
+        importedIndices: importedIndices,
+        useDeferredStrategy: useDeferredSourceReads);
     backendStrategy.prepareCodegenReader(source);
     Map<MemberEntity, CodegenResult> codegenResults =
         source.readMemberMap((MemberEntity member) {
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index e608892..85797a1 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -2097,7 +2097,7 @@
 
     IsTestSpecialization specialization =
         SpecializedChecks.findIsTestSpecialization(
-            node.dartType, _graph, _closedWorld);
+            node.dartType, _graph.element, _closedWorld);
 
     if (specialization == IsTestSpecialization.isNull ||
         specialization == IsTestSpecialization.notNull) {
diff --git a/pkg/compiler/lib/src/universe/member_usage.dart b/pkg/compiler/lib/src/universe/member_usage.dart
index 9bb2a33..84df30c 100644
--- a/pkg/compiler/lib/src/universe/member_usage.dart
+++ b/pkg/compiler/lib/src/universe/member_usage.dart
@@ -2,14 +2,12 @@
 // 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.10
-
 import 'dart:math' as Math;
 
 import '../common.dart';
 import '../constants/values.dart';
 import '../elements/entities.dart';
-import '../js_model/closure.dart' show JRecordField;
+import '../js_model/jrecord_field_interface.dart' show JRecordFieldInterface;
 import '../serialization/serialization_interfaces.dart';
 import '../util/enumset.dart';
 import 'call_structure.dart';
@@ -52,10 +50,10 @@
   MemberUsage.cloned(this.entity, EnumSet<MemberUse> pendingUse)
       : super.cloned(pendingUse);
 
-  factory MemberUsage(MemberEntity member, {MemberAccess potentialAccess}) {
+  factory MemberUsage(MemberEntity member, {MemberAccess? potentialAccess}) {
     /// Create the set of potential accesses to [member], limited to [original]
     /// if provided.
-    EnumSet<Access> createPotentialAccessSet(EnumSet<Access> original) {
+    EnumSet<Access> createPotentialAccessSet(EnumSet<Access>? original) {
       if (original != null) {
         if (original.isEmpty) return emptySet;
         return original.clone();
@@ -66,7 +64,7 @@
       } else if (member.isInstanceMember) {
         return EnumSet.fromValues(Access.values);
       } else {
-        assert(member is JRecordField, "Unexpected member: $member");
+        assert(member is JRecordFieldInterface, "Unexpected member: $member");
         return EnumSet();
       }
     }
@@ -89,7 +87,7 @@
       return createPotentialAccessSet(potentialAccess?.invokes);
     }
 
-    if (member.isField) {
+    if (member is FieldEntity) {
       if (member.isAssignable) {
         return FieldUsage(member,
             potentialReads: createPotentialReads(),
@@ -101,33 +99,35 @@
             potentialWrites: emptySet,
             potentialInvokes: createPotentialInvokes());
       }
-    } else if (member.isGetter) {
-      return PropertyUsage(member,
-          potentialReads: createPotentialReads(),
-          potentialWrites: emptySet,
-          potentialInvokes: createPotentialInvokes());
-    } else if (member.isSetter) {
-      return PropertyUsage(member,
-          potentialReads: emptySet,
-          potentialWrites: createPotentialWrites(),
-          potentialInvokes: emptySet);
-    } else if (member.isConstructor) {
-      return MethodUsage(member,
-          potentialReads: emptySet, potentialInvokes: createPotentialInvokes());
-    } else {
-      assert(member is FunctionEntity,
-          failedAt(member, "Unexpected member: $member"));
-      return MethodUsage(member,
-          potentialReads: createPotentialReads(),
-          potentialInvokes: createPotentialInvokes());
+    } else if (member is FunctionEntity) {
+      if (member.isGetter) {
+        return PropertyUsage(member,
+            potentialReads: createPotentialReads(),
+            potentialWrites: emptySet,
+            potentialInvokes: createPotentialInvokes());
+      } else if (member.isSetter) {
+        return PropertyUsage(member,
+            potentialReads: emptySet,
+            potentialWrites: createPotentialWrites(),
+            potentialInvokes: emptySet);
+      } else if (member.isConstructor) {
+        return MethodUsage(member,
+            potentialReads: emptySet,
+            potentialInvokes: createPotentialInvokes());
+      } else {
+        return MethodUsage(member,
+            potentialReads: createPotentialReads(),
+            potentialInvokes: createPotentialInvokes());
+      }
     }
+    throw failedAt(member, "Unexpected member: $member");
   }
 
   /// `true` if [entity] has been initialized.
   bool get hasInit => true;
 
   /// The set of constant initial values for a field.
-  Iterable<ConstantValue> get initialConstants => null;
+  Iterable<ConstantValue>? get initialConstants => null;
 
   /// `true` if [entity] has been read as a value. For a field this is a normal
   /// read access, for a function this is a closurization.
@@ -165,7 +165,7 @@
   /// Returns the [ParameterStructure] corresponding to the parameters that are
   /// used in invocations of [entity]. For a field, getter or setter this is
   /// always `null`.
-  ParameterStructure get invokedParameters => null;
+  ParameterStructure? get invokedParameters => null;
 
   /// Whether this member has any potential but unregistered dynamic reads,
   /// writes or invocations.
@@ -279,28 +279,32 @@
   final EnumSet<Access> invokes;
 
   PropertyUsage.cloned(MemberEntity member, EnumSet<MemberUse> pendingUse,
-      {this.potentialReads,
-      this.potentialWrites,
-      this.potentialInvokes,
-      this.reads,
-      this.writes,
-      this.invokes})
-      : assert(potentialReads != null),
-        assert(potentialWrites != null),
-        assert(potentialInvokes != null),
-        assert(reads != null),
-        assert(writes != null),
-        assert(invokes != null),
+      {required this.potentialReads,
+      required this.potentialWrites,
+      required this.potentialInvokes,
+      required this.reads,
+      required this.writes,
+      required this.invokes})
+      : // TODO(48820): Remove asserts when sound.
+        assert((potentialReads as dynamic) != null),
+        assert((potentialWrites as dynamic) != null),
+        assert((potentialInvokes as dynamic) != null),
+        assert((reads as dynamic) != null),
+        assert((writes as dynamic) != null),
+        assert((invokes as dynamic) != null),
         super.cloned(member, pendingUse);
 
   PropertyUsage(MemberEntity member,
-      {this.potentialReads, this.potentialWrites, this.potentialInvokes})
+      {required this.potentialReads,
+      required this.potentialWrites,
+      required this.potentialInvokes})
       : reads = EnumSet(),
         writes = EnumSet(),
         invokes = EnumSet(),
-        assert(potentialReads != null),
-        assert(potentialWrites != null),
-        assert(potentialInvokes != null),
+        // TODO(48820): Remove asserts when sound.
+        assert((potentialReads as dynamic) != null),
+        assert((potentialWrites as dynamic) != null),
+        assert((potentialInvokes as dynamic) != null),
         super.internal(member);
 
   @override
@@ -384,33 +388,37 @@
   @override
   final EnumSet<Access> writes;
 
-  List<ConstantValue> _initialConstants;
+  List<ConstantValue>? _initialConstants;
 
   FieldUsage.cloned(FieldEntity field, EnumSet<MemberUse> pendingUse,
-      {this.potentialReads,
-      this.potentialWrites,
-      this.potentialInvokes,
-      this.hasInit,
-      this.reads,
-      this.writes,
-      this.invokes})
-      : assert(potentialReads != null),
-        assert(potentialWrites != null),
-        assert(potentialInvokes != null),
-        assert(reads != null),
-        assert(writes != null),
-        assert(invokes != null),
+      {required this.potentialReads,
+      required this.potentialWrites,
+      required this.potentialInvokes,
+      required this.hasInit,
+      required this.reads,
+      required this.writes,
+      required this.invokes})
+      : // TODO(48820): Remove asserts when sound.
+        assert((potentialReads as dynamic) != null),
+        assert((potentialWrites as dynamic) != null),
+        assert((potentialInvokes as dynamic) != null),
+        assert((reads as dynamic) != null),
+        assert((writes as dynamic) != null),
+        assert((invokes as dynamic) != null),
         super.cloned(field, pendingUse);
 
   FieldUsage(FieldEntity field,
-      {this.potentialReads, this.potentialWrites, this.potentialInvokes})
+      {required this.potentialReads,
+      required this.potentialWrites,
+      required this.potentialInvokes})
       : hasInit = false,
         reads = EnumSet(),
         writes = EnumSet(),
         invokes = EnumSet(),
-        assert(potentialReads != null),
-        assert(potentialWrites != null),
-        assert(potentialInvokes != null),
+        // TODO(48820): Remove asserts when sound.
+        assert((potentialReads as dynamic) != null),
+        assert((potentialWrites as dynamic) != null),
+        assert((potentialInvokes as dynamic) != null),
         super.internal(field);
 
   @override
@@ -427,8 +435,7 @@
 
   @override
   EnumSet<MemberUse> constantInit(ConstantValue constant) {
-    _initialConstants ??= [];
-    _initialConstants.add(constant);
+    (_initialConstants ??= []).add(constant);
     return init();
   }
 
@@ -475,7 +482,7 @@
 
   @override
   MemberUsage clone() {
-    return FieldUsage.cloned(entity, _pendingUse.clone(),
+    return FieldUsage.cloned(entity as FieldEntity, _pendingUse.clone(),
         potentialReads: potentialReads.clone(),
         potentialWrites: potentialWrites.clone(),
         potentialInvokes: potentialInvokes.clone(),
@@ -515,20 +522,23 @@
 
   MethodUsage.cloned(FunctionEntity function, this.parameterUsage,
       EnumSet<MemberUse> pendingUse,
-      {this.potentialReads, this.reads, this.potentialInvokes, this.invokes})
-      : assert(potentialReads != null),
-        assert(potentialInvokes != null),
-        assert(reads != null),
-        assert(invokes != null),
+      {required this.potentialReads,
+      required this.reads,
+      required this.potentialInvokes,
+      required this.invokes})
+      : assert((potentialReads as dynamic) != null),
+        assert((potentialInvokes as dynamic) != null),
+        assert((reads as dynamic) != null),
+        assert((invokes as dynamic) != null),
         super.cloned(function, pendingUse);
 
   MethodUsage(FunctionEntity function,
-      {this.potentialReads, this.potentialInvokes})
+      {required this.potentialReads, required this.potentialInvokes})
       : reads = EnumSet(),
         invokes = EnumSet(),
         parameterUsage = ParameterUsage(function.parameterStructure),
-        assert(potentialReads != null),
-        assert(potentialInvokes != null),
+        assert((potentialReads as dynamic) != null),
+        assert((potentialInvokes as dynamic) != null),
         super.internal(function);
 
   @override
@@ -576,7 +586,7 @@
   }
 
   @override
-  ParameterStructure get invokedParameters => parameterUsage.invokedParameters;
+  ParameterStructure? get invokedParameters => parameterUsage.invokedParameters;
 
   @override
   bool get hasPendingDynamicInvoke =>
@@ -586,7 +596,7 @@
   @override
   MemberUsage clone() {
     return MethodUsage.cloned(
-        entity, parameterUsage.clone(), _pendingUse.clone(),
+        entity as FunctionEntity, parameterUsage.clone(), _pendingUse.clone(),
         reads: reads.clone(),
         potentialReads: potentialReads.clone(),
         invokes: invokes.clone(),
@@ -682,26 +692,25 @@
   final ParameterStructure _parameterStructure;
 
   /// `true` if the method or constructor has at least one invocation.
-  bool _hasInvoke;
+  bool _hasInvoke = false;
 
   /// The maximum number of (optional) positional parameters provided in
   /// invocations of the method or constructor.
   ///
   /// If all positional parameters having been provided this is set to `null`.
-  int _providedPositionalParameters;
+  int? _providedPositionalParameters;
 
   /// `true` if all type parameters have been provided in at least one
   /// invocation of the method or constructor.
-  bool _areAllTypeParametersProvided;
+  late bool _areAllTypeParametersProvided;
 
   /// The set of named parameters that have not yet been provided in any
   /// invocation of the method or constructor.
   ///
   /// If all named parameters have been provided this is set to `null`.
-  Set<String> _unprovidedNamedParameters;
+  Set<String>? _unprovidedNamedParameters;
 
   ParameterUsage(this._parameterStructure) {
-    _hasInvoke = false;
     _areAllTypeParametersProvided = _parameterStructure.typeParameters == 0;
     _providedPositionalParameters = _parameterStructure.positionalParameters ==
             _parameterStructure.requiredPositionalParameters
@@ -714,10 +723,10 @@
   }
 
   ParameterUsage.cloned(this._parameterStructure,
-      {bool hasInvoke,
-      int providedPositionalParameters,
-      bool areAllTypeParametersProvided,
-      Set<String> unprovidedNamedParameters})
+      {required bool hasInvoke,
+      required providedPositionalParameters,
+      required bool areAllTypeParametersProvided,
+      required Set<String>? unprovidedNamedParameters})
       : _hasInvoke = hasInvoke,
         _providedPositionalParameters = providedPositionalParameters,
         _areAllTypeParametersProvided = areAllTypeParametersProvided,
@@ -729,22 +738,23 @@
     bool changed = false;
     if (_providedPositionalParameters != null) {
       int newProvidedPositionalParameters = Math.max(
-          _providedPositionalParameters, callStructure.positionalArgumentCount);
+          _providedPositionalParameters!,
+          callStructure.positionalArgumentCount);
       changed |=
           newProvidedPositionalParameters != _providedPositionalParameters;
       _providedPositionalParameters = newProvidedPositionalParameters;
-      if (_providedPositionalParameters >=
+      if (_providedPositionalParameters! >=
           _parameterStructure.positionalParameters) {
         _providedPositionalParameters = null;
       }
     }
     if (_unprovidedNamedParameters != null &&
         callStructure.namedArguments.isNotEmpty) {
-      int _providedNamedParametersCount = _unprovidedNamedParameters.length;
-      _unprovidedNamedParameters.removeAll(callStructure.namedArguments);
+      int _providedNamedParametersCount = _unprovidedNamedParameters!.length;
+      _unprovidedNamedParameters!.removeAll(callStructure.namedArguments);
       changed |=
-          _providedNamedParametersCount != _unprovidedNamedParameters.length;
-      if (_unprovidedNamedParameters.isEmpty) {
+          _providedNamedParametersCount != _unprovidedNamedParameters!.length;
+      if (_unprovidedNamedParameters!.isEmpty) {
         _unprovidedNamedParameters = null;
       }
     }
@@ -770,7 +780,7 @@
     _areAllTypeParametersProvided = true;
   }
 
-  ParameterStructure get invokedParameters {
+  ParameterStructure? get invokedParameters {
     if (!_hasInvoke) return null;
     if (isFullyUsed) return _parameterStructure;
     return ParameterStructure(
@@ -780,7 +790,7 @@
         _unprovidedNamedParameters == null
             ? _parameterStructure.namedParameters
             : _parameterStructure.namedParameters
-                .where((n) => !_unprovidedNamedParameters.contains(n))
+                .where((n) => !_unprovidedNamedParameters!.contains(n))
                 .toList(),
         _parameterStructure.requiredNamedParameters,
         _areAllTypeParametersProvided ? _parameterStructure.typeParameters : 0);
diff --git a/pkg/compiler/lib/src/universe/strong_mode_constraint.dart b/pkg/compiler/lib/src/universe/strong_mode_constraint.dart
new file mode 100644
index 0000000..571df2e
--- /dev/null
+++ b/pkg/compiler/lib/src/universe/strong_mode_constraint.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// TODO(48820): delete this class once migration is complete.
+abstract class StrongModeConstraintInterface {
+  bool get isThis;
+  bool get isExact;
+  String get className;
+}
diff --git a/pkg/compiler/lib/src/universe/use.dart b/pkg/compiler/lib/src/universe/use.dart
index 28fdeed..ed7a7c3 100644
--- a/pkg/compiler/lib/src/universe/use.dart
+++ b/pkg/compiler/lib/src/universe/use.dart
@@ -2,8 +2,6 @@
 // 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.10
-
 /// This library defines individual world impacts.
 ///
 /// We call these building blocks `uses`. Each `use` is a single impact of the
@@ -24,11 +22,11 @@
 import '../elements/entities.dart';
 import '../inferrer/abstract_value_domain.dart';
 import '../serialization/serialization_interfaces.dart';
-import '../js_model/closure.dart' show JRecordField;
+import '../js_model/jrecord_field_interface.dart' show JRecordFieldInterface;
 import '../util/util.dart' show equalElements, Hashing;
 import 'call_structure.dart' show CallStructure;
 import 'selector.dart' show Selector;
-import 'world_builder.dart' show StrongModeConstraint;
+import 'strong_mode_constraint.dart' show StrongModeConstraintInterface;
 
 enum DynamicUseKind {
   INVOKE,
@@ -43,8 +41,8 @@
   static const String tag = 'dynamic-use';
 
   final Selector selector;
-  final Object receiverConstraint;
-  final List<DartType> _typeArguments;
+  final Object? receiverConstraint;
+  final List<DartType>? _typeArguments;
 
   DynamicUse(this.selector, this.receiverConstraint, this._typeArguments)
       : assert(
@@ -65,11 +63,11 @@
     source.begin(tag);
     Selector selector = Selector.readFromDataSource(source);
     bool hasConstraint = source.readBool();
-    Object receiverConstraint;
+    Object? receiverConstraint;
     if (hasConstraint) {
       receiverConstraint = source.readAbstractValue();
     }
-    List<DartType> typeArguments = source.readDartTypesOrNull();
+    List<DartType>? typeArguments = source.readDartTypesOrNull();
     source.end(tag);
     return DynamicUse(selector, receiverConstraint, typeArguments);
   }
@@ -77,13 +75,14 @@
   void writeToDataSink(DataSinkWriter sink) {
     sink.begin(tag);
     selector.writeToDataSink(sink);
-    sink.writeBool(receiverConstraint != null);
-    if (receiverConstraint != null) {
-      if (receiverConstraint is AbstractValue) {
-        sink.writeAbstractValue(receiverConstraint);
+    var constraint = receiverConstraint;
+    sink.writeBool(constraint != null);
+    if (constraint != null) {
+      if (constraint is AbstractValue) {
+        sink.writeAbstractValue(constraint);
       } else {
         throw UnsupportedError(
-            "Unsupported receiver constraint: ${receiverConstraint}");
+            "Unsupported receiver constraint: ${constraint}");
       }
     }
     sink.writeDartTypesOrNull(_typeArguments);
@@ -95,20 +94,20 @@
     StringBuffer sb = StringBuffer();
     if (receiverConstraint != null) {
       var constraint = receiverConstraint;
-      if (constraint is StrongModeConstraint) {
+      if (constraint is StrongModeConstraintInterface) {
         if (constraint.isThis) {
           sb.write('this:');
         } else if (constraint.isExact) {
           sb.write('exact:');
         }
-        sb.write(constraint.cls.name);
+        sb.write(constraint.className);
       } else {
         sb.write(constraint);
       }
       sb.write('.');
     }
     sb.write(selector.name);
-    if (typeArguments != null && typeArguments.isNotEmpty) {
+    if (typeArguments.isNotEmpty) {
       sb.write('<');
       sb.write(typeArguments.join(','));
       sb.write('>');
@@ -183,11 +182,11 @@
   final StaticUseKind kind;
   @override
   final int hashCode;
-  final InterfaceType type;
-  final CallStructure callStructure;
-  final ImportEntity deferredImport;
-  final ConstantValue constant;
-  final List<DartType> typeArguments;
+  final InterfaceType? type;
+  final CallStructure? callStructure;
+  final ImportEntity? deferredImport;
+  final ConstantValue? constant;
+  final List<DartType>? typeArguments;
 
   StaticUse.internal(Entity element, this.kind,
       {this.type,
@@ -221,13 +220,12 @@
     source.begin(tag);
     MemberEntity element = source.readMember();
     StaticUseKind kind = source.readEnum(StaticUseKind.values);
-    InterfaceType /*?*/ type =
-        source.readDartTypeOrNull() as InterfaceType /*?*/;
-    CallStructure callStructure =
+    InterfaceType? type = source.readDartTypeOrNull() as InterfaceType?;
+    CallStructure? callStructure =
         source.readValueOrNull(() => CallStructure.readFromDataSource(source));
-    ImportEntity deferredImport = source.readImportOrNull();
-    ConstantValue constant = source.readConstantOrNull();
-    List<DartType> typeArguments = source.readDartTypesOrNull();
+    ImportEntity? deferredImport = source.readImportOrNull();
+    ConstantValue? constant = source.readConstantOrNull();
+    List<DartType>? typeArguments = source.readDartTypesOrNull();
     source.end(tag);
     return StaticUse.internal(element, kind,
         type: type,
@@ -239,8 +237,7 @@
 
   void writeToDataSink(DataSinkWriter sink) {
     sink.begin(tag);
-    assert(element is MemberEntity, "Unsupported entity: $element");
-    sink.writeMember(element);
+    sink.writeMember(element as MemberEntity);
     sink.writeEnum(kind);
     sink.writeDartTypeOrNull(type);
     sink.writeValueOrNull(
@@ -269,40 +266,41 @@
         break;
       default:
     }
-    if (element is MemberEntity) {
-      MemberEntity member = element;
+    final member = element;
+    if (member is MemberEntity) {
       if (member.enclosingClass != null) {
-        sb.write(member.enclosingClass.name);
+        sb.write(member.enclosingClass!.name);
         sb.write('.');
       }
     }
-    if (element.name == null) {
+    if (member.name == null) {
       sb.write('<anonymous>');
     } else {
-      sb.write(element.name);
+      sb.write(member.name);
     }
-    if (typeArguments != null && typeArguments.isNotEmpty) {
+    if (typeArguments != null && typeArguments!.isNotEmpty) {
       sb.write('<');
-      sb.write(typeArguments.join(','));
+      sb.write(typeArguments!.join(','));
       sb.write('>');
     }
-    if (callStructure != null) {
+    final callStructureLocal = callStructure;
+    if (callStructureLocal != null) {
       sb.write('(');
-      sb.write(callStructure.positionalArgumentCount);
-      if (callStructure.namedArgumentCount > 0) {
+      sb.write(callStructureLocal.positionalArgumentCount);
+      if (callStructureLocal.namedArgumentCount > 0) {
         sb.write(',');
-        sb.write(callStructure.getOrderedNamedArguments().join(','));
+        sb.write(callStructureLocal.getOrderedNamedArguments().join(','));
       }
       sb.write(')');
     }
     if (deferredImport != null) {
       sb.write('{');
-      sb.write(deferredImport.name);
+      sb.write(deferredImport!.name);
       sb.write('}');
     }
     if (constant != null) {
       sb.write('=');
-      sb.write(constant.toStructuredText(null));
+      sb.write(constant!.toStructuredText(null));
     }
     return sb.toString();
   }
@@ -311,7 +309,7 @@
   /// [callStructure].
   factory StaticUse.staticInvoke(
       FunctionEntity element, CallStructure callStructure,
-      [List<DartType> typeArguments, ImportEntity deferredImport]) {
+      [List<DartType>? typeArguments, ImportEntity? deferredImport]) {
     assert(
         element.isStatic || element.isTopLevel,
         failedAt(
@@ -321,10 +319,9 @@
     assert(element.isFunction,
         failedAt(element, "Static get element $element must be a function."));
     assert(
-        callStructure != null,
+        (callStructure as dynamic) != null, // TODO(48820): remove when sound
         failedAt(element,
             "Not CallStructure for static invocation of element $element."));
-
     StaticUse staticUse = StaticUse.internal(
         element, StaticUseKind.STATIC_INVOKE,
         callStructure: callStructure,
@@ -336,7 +333,7 @@
 
   /// Closurization of a static or top-level function [element].
   factory StaticUse.staticTearOff(FunctionEntity element,
-      [ImportEntity deferredImport]) {
+      [ImportEntity? deferredImport]) {
     assert(
         element.isStatic || element.isTopLevel,
         failedAt(
@@ -351,7 +348,7 @@
 
   /// Read access of a static or top-level field or getter [element].
   factory StaticUse.staticGet(MemberEntity element,
-      [ImportEntity deferredImport]) {
+      [ImportEntity? deferredImport]) {
     assert(
         element.isStatic || element.isTopLevel,
         failedAt(
@@ -368,7 +365,7 @@
 
   /// Write access of a static or top-level field or setter [element].
   factory StaticUse.staticSet(MemberEntity element,
-      [ImportEntity deferredImport]) {
+      [ImportEntity? deferredImport]) {
     assert(
         element.isStatic || element.isTopLevel,
         failedAt(
@@ -400,13 +397,13 @@
   /// Invocation of a super method [element] with the given [callStructure].
   factory StaticUse.superInvoke(
       FunctionEntity element, CallStructure callStructure,
-      [List<DartType> typeArguments]) {
+      [List<DartType>? typeArguments]) {
     assert(
         element.isInstanceMember,
         failedAt(element,
             "Super invoke element $element must be an instance method."));
     assert(
-        callStructure != null,
+        (callStructure as dynamic) != null, // TODO(48820): remove when sound
         failedAt(element,
             "Not CallStructure for super invocation of element $element."));
     StaticUse staticUse = StaticUse.internal(
@@ -471,7 +468,7 @@
             "Constructor invoke element $element must be a "
             "generative constructor."));
     assert(
-        callStructure != null,
+        (callStructure as dynamic) != null, // TODO(48820): remove when sound
         failedAt(
             element,
             "Not CallStructure for super constructor invocation of element "
@@ -485,7 +482,7 @@
   factory StaticUse.constructorBodyInvoke(
       ConstructorBodyEntity element, CallStructure callStructure) {
     assert(
-        callStructure != null,
+        (callStructure as dynamic) != null, // TODO(48820): remove when sound
         failedAt(
             element,
             "Not CallStructure for constructor body invocation of element "
@@ -549,7 +546,7 @@
         failedAt(element,
             "Constructor invocation element $element must be a constructor."));
     assert(
-        callStructure != null,
+        (callStructure as dynamic) != null, // TODO(48820): remove when sound
         failedAt(
             element,
             "Not CallStructure for constructor invocation of element "
@@ -565,7 +562,8 @@
       CallStructure callStructure,
       InterfaceType type,
       ImportEntity deferredImport) {
-    assert(type != null,
+    assert(
+        (type as dynamic) != null, // TODO(48820): remove when sound
         failedAt(element, "No type provided for constructor invocation."));
     assert(
         element.isConstructor,
@@ -586,7 +584,8 @@
       CallStructure callStructure,
       InterfaceType type,
       ImportEntity deferredImport) {
-    assert(type != null,
+    assert(
+        (type as dynamic) != null, // TODO(48820): remove when sound
         failedAt(element, "No type provided for constructor invocation."));
     assert(
         element.isConstructor,
@@ -623,7 +622,7 @@
   /// Read access of an instance field or boxed field [element].
   factory StaticUse.fieldGet(FieldEntity element) {
     assert(
-        element.isInstanceMember || element is JRecordField,
+        element.isInstanceMember || element is JRecordFieldInterface,
         failedAt(element,
             "Field init element $element must be an instance or boxed field."));
     return StaticUse.internal(element, StaticUseKind.INSTANCE_FIELD_GET);
@@ -632,7 +631,7 @@
   /// Write access of an instance field or boxed field [element].
   factory StaticUse.fieldSet(FieldEntity element) {
     assert(
-        element.isInstanceMember || element is JRecordField,
+        element.isInstanceMember || element is JRecordFieldInterface,
         failedAt(element,
             "Field init element $element must be an instance or boxed field."));
     return StaticUse.internal(element, StaticUseKind.INSTANCE_FIELD_SET);
@@ -723,7 +722,7 @@
   final TypeUseKind kind;
   @override
   final int hashCode;
-  final ImportEntity deferredImport;
+  final ImportEntity? deferredImport;
 
   TypeUse.internal(DartType type, TypeUseKind kind, [this.deferredImport])
       : this.type = type,
@@ -734,7 +733,7 @@
     source.begin(tag);
     DartType type = source.readDartType();
     TypeUseKind kind = source.readEnum(TypeUseKind.values);
-    ImportEntity deferredImport = source.readImportOrNull();
+    ImportEntity? deferredImport = source.readImportOrNull();
     source.end(tag);
     return TypeUse.internal(type, kind, deferredImport);
   }
@@ -797,7 +796,7 @@
     sb.write(type);
     if (deferredImport != null) {
       sb.write('{');
-      sb.write(deferredImport.name);
+      sb.write(deferredImport!.name);
       sb.write('}');
     }
     return sb.toString();
diff --git a/pkg/compiler/lib/src/universe/world_builder.dart b/pkg/compiler/lib/src/universe/world_builder.dart
index 64a68ab..7d3e254 100644
--- a/pkg/compiler/lib/src/universe/world_builder.dart
+++ b/pkg/compiler/lib/src/universe/world_builder.dart
@@ -16,6 +16,7 @@
 import '../world.dart' show World;
 import 'selector.dart' show Selector;
 import 'use.dart' show DynamicUse, StaticUse;
+import 'strong_mode_constraint.dart' show StrongModeConstraintInterface;
 
 /// The combined constraints on receivers all the dynamic call sites of the same
 /// selector.
@@ -167,7 +168,7 @@
   }
 }
 
-class StrongModeConstraint {
+class StrongModeConstraint implements StrongModeConstraintInterface {
   final ClassEntity cls;
   final ClassRelation relation;
 
@@ -191,11 +192,16 @@
     return world.isInheritedIn(element, cls, relation);
   }
 
+  @override
   bool get isExact => relation == ClassRelation.exact;
 
+  @override
   bool get isThis => relation == ClassRelation.thisExpression;
 
   @override
+  String get className => cls.name;
+
+  @override
   bool operator ==(Object other) {
     if (identical(this, other)) return true;
     return other is StrongModeConstraint &&
diff --git a/pkg/compiler/lib/src/universe/world_impact.dart b/pkg/compiler/lib/src/universe/world_impact.dart
index a4a4c43..0d91e03 100644
--- a/pkg/compiler/lib/src/universe/world_impact.dart
+++ b/pkg/compiler/lib/src/universe/world_impact.dart
@@ -2,8 +2,6 @@
 // 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.10
-
 library dart2js.universe.world_impact;
 
 import '../elements/entities.dart';
@@ -24,7 +22,9 @@
 class WorldImpact {
   const WorldImpact();
 
-  MemberEntity get member => null;
+  /// [member] may be `null` when the impact is for something that is not a
+  /// member, e.g. a constant, or external or native dependencies.
+  MemberEntity? get member => null;
 
   Iterable<DynamicUse> get dynamicUses => const [];
 
@@ -39,16 +39,17 @@
 
   Iterable<ConstantUse> get constantUses => const [];
 
-  void _forEach<U>(Iterable<U> uses, void Function(MemberEntity, U) visitUse) =>
+  void _forEach<U>(
+          Iterable<U> uses, void Function(MemberEntity?, U) visitUse) =>
       uses.forEach((use) => visitUse(member, use));
 
-  void forEachDynamicUse(void Function(MemberEntity, DynamicUse) visitUse) =>
+  void forEachDynamicUse(void Function(MemberEntity?, DynamicUse) visitUse) =>
       _forEach(dynamicUses, visitUse);
-  void forEachStaticUse(void Function(MemberEntity, StaticUse) visitUse) =>
+  void forEachStaticUse(void Function(MemberEntity?, StaticUse) visitUse) =>
       _forEach(staticUses, visitUse);
-  void forEachTypeUse(void Function(MemberEntity, TypeUse) visitUse) =>
+  void forEachTypeUse(void Function(MemberEntity?, TypeUse) visitUse) =>
       _forEach(typeUses, visitUse);
-  void forEachConstantUse(void Function(MemberEntity, ConstantUse) visitUse) =>
+  void forEachConstantUse(void Function(MemberEntity?, ConstantUse) visitUse) =>
       _forEach(constantUses, visitUse);
 
   bool get isEmpty => true;
@@ -89,14 +90,14 @@
 class WorldImpactBuilderImpl extends WorldImpactBuilder {
   /// The [MemberEntity] associated with this set of impacts. Maybe null.
   @override
-  final MemberEntity member;
+  final MemberEntity? member;
 
   // TODO(johnniwinther): Do we benefit from lazy initialization of the
   // [Setlet]s?
-  Set<DynamicUse> _dynamicUses;
-  Set<StaticUse> _staticUses;
-  Set<TypeUse> _typeUses;
-  Set<ConstantUse> _constantUses;
+  Set<DynamicUse>? _dynamicUses;
+  Set<StaticUse>? _staticUses;
+  Set<TypeUse>? _typeUses;
+  Set<ConstantUse>? _constantUses;
 
   WorldImpactBuilderImpl([this.member]);
 
@@ -122,9 +123,8 @@
 
   @override
   void registerDynamicUse(DynamicUse dynamicUse) {
-    assert(dynamicUse != null);
-    _dynamicUses ??= Setlet();
-    _dynamicUses.add(dynamicUse);
+    assert((dynamicUse as dynamic) != null); // TODO(48820): Remove when sound.
+    (_dynamicUses ??= Setlet()).add(dynamicUse);
   }
 
   @override
@@ -134,9 +134,8 @@
 
   @override
   void registerTypeUse(TypeUse typeUse) {
-    assert(typeUse != null);
-    _typeUses ??= Setlet();
-    _typeUses.add(typeUse);
+    assert((typeUse as dynamic) != null); // TODO(48820): Remove when sound.
+    (_typeUses ??= Setlet()).add(typeUse);
   }
 
   @override
@@ -146,9 +145,8 @@
 
   @override
   void registerStaticUse(StaticUse staticUse) {
-    assert(staticUse != null);
-    _staticUses ??= Setlet();
-    _staticUses.add(staticUse);
+    assert((staticUse as dynamic) != null); // TODO(48820): Remove when sound.
+    (_staticUses ??= Setlet()).add(staticUse);
   }
 
   @override
@@ -158,9 +156,8 @@
 
   @override
   void registerConstantUse(ConstantUse constantUse) {
-    assert(constantUse != null);
-    _constantUses ??= Setlet();
-    _constantUses.add(constantUse);
+    assert((constantUse as dynamic) != null); // TODO(48820): Remove when sound.
+    (_constantUses ??= Setlet()).add(constantUse);
   }
 
   @override
@@ -174,15 +171,15 @@
 class TransformedWorldImpact extends WorldImpactBuilder {
   final WorldImpact worldImpact;
 
-  Setlet<StaticUse> _staticUses;
-  Setlet<TypeUse> _typeUses;
-  Setlet<DynamicUse> _dynamicUses;
-  Setlet<ConstantUse> _constantUses;
+  Setlet<StaticUse>? _staticUses;
+  Setlet<TypeUse>? _typeUses;
+  Setlet<DynamicUse>? _dynamicUses;
+  Setlet<ConstantUse>? _constantUses;
 
   TransformedWorldImpact(this.worldImpact);
 
   @override
-  MemberEntity get member => worldImpact.member;
+  MemberEntity? get member => worldImpact.member;
 
   @override
   bool get isEmpty {
@@ -201,13 +198,13 @@
   @override
   void registerDynamicUse(DynamicUse dynamicUse) {
     _dynamicUses ??= Setlet.of(worldImpact.dynamicUses);
-    _dynamicUses.add(dynamicUse);
+    _dynamicUses!.add(dynamicUse);
   }
 
   @override
   void registerTypeUse(TypeUse typeUse) {
     _typeUses ??= Setlet.of(worldImpact.typeUses);
-    _typeUses.add(typeUse);
+    _typeUses!.add(typeUse);
   }
 
   @override
@@ -218,7 +215,7 @@
   @override
   void registerStaticUse(StaticUse staticUse) {
     _staticUses ??= Setlet.of(worldImpact.staticUses);
-    _staticUses.add(staticUse);
+    _staticUses!.add(staticUse);
   }
 
   @override
@@ -234,7 +231,7 @@
   @override
   void registerConstantUse(ConstantUse constantUse) {
     _constantUses ??= Setlet.of(worldImpact.constantUses);
-    _constantUses.add(constantUse);
+    _constantUses!.add(constantUse);
   }
 
   @override
diff --git a/pkg/dev_compiler/lib/src/compiler/shared_command.dart b/pkg/dev_compiler/lib/src/compiler/shared_command.dart
index 97e214a..ffa5832 100644
--- a/pkg/dev_compiler/lib/src/compiler/shared_command.dart
+++ b/pkg/dev_compiler/lib/src/compiler/shared_command.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io';
 
 import 'package:args/args.dart';
@@ -84,7 +82,7 @@
   final String multiRootScheme;
 
   /// Path to set multi-root files relative to when generating source-maps.
-  final String multiRootOutputPath;
+  final String? multiRootOutputPath;
 
   /// Experimental language features that are enabled/disabled, see
   /// [the spec](https://github.com/dart-lang/sdk/blob/master/docs/process/experimental-flags.md)
@@ -107,8 +105,8 @@
       this.emitFullCompiledKernel = false,
       this.summaryModules = const {},
       this.moduleFormats = const [],
-      this.moduleName,
-      this.multiRootScheme,
+      required this.moduleName,
+      this.multiRootScheme = 'org-dartlang-app',
       this.multiRootOutputPath,
       this.experiments = const {},
       this.soundNullSafety = false,
@@ -132,7 +130,7 @@
             moduleFormats: parseModuleFormatOption(args),
             moduleName: _getModuleName(args),
             multiRootScheme: args['multi-root-scheme'] as String,
-            multiRootOutputPath: args['multi-root-output-path'] as String,
+            multiRootOutputPath: args['multi-root-output-path'] as String?,
             experiments: parseExperimentalArguments(
                 args['enable-experiment'] as List<String>),
             soundNullSafety: args['sound-null-safety'] as bool,
@@ -147,7 +145,7 @@
             moduleName:
                 args['module-name'] != null ? _getModuleName(args) : 'dart_sdk',
             multiRootScheme: args['multi-root-scheme'] as String,
-            multiRootOutputPath: args['multi-root-output-path'] as String,
+            multiRootOutputPath: args['multi-root-output-path'] as String?,
             experiments: parseExperimentalArguments(
                 args['enable-experiment'] as List<String>),
             soundNullSafety: args['sound-null-safety'] as bool,
@@ -230,18 +228,15 @@
   }
 
   static String _getModuleName(ArgResults args) {
-    var moduleName = args['module-name'] as String;
+    var moduleName = args['module-name'] as String?;
     if (moduleName == null) {
-      var outPaths = args['out'];
-      var outPath = outPaths is String
-          ? outPaths
-          : (outPaths as List<String>)
-              .firstWhere((_) => true, orElse: () => null);
-
-      // TODO(jmesserly): fix the debugger console so it's not passing invalid
-      // options.
-      if (outPath == null) return null;
-
+      var outPaths = args['out'] as List<String>;
+      if (outPaths.isEmpty) {
+        throw UnsupportedError(
+            'No module name provided and unable to synthesize one without any '
+            'output paths.');
+      }
+      var outPath = outPaths.first;
       moduleName = p.basenameWithoutExtension(outPath);
     }
     // TODO(jmesserly): this should probably use sourcePathToUri.
@@ -260,9 +255,8 @@
 /// allow working with summaries whose physical location is outside of the
 /// module root directory.
 Map<String, String> _parseCustomSummaryModules(List<String> summaryPaths,
-    [String moduleRoot, String summaryExt]) {
+    [String? moduleRoot, String? summaryExt]) {
   var pathToModule = <String, String>{};
-  if (summaryPaths == null) return pathToModule;
   for (var summaryPath in summaryPaths) {
     var equalSign = summaryPath.indexOf('=');
     String modulePath;
@@ -299,7 +293,7 @@
     if (abbreviation != null) {
       knownAbbreviations.add(abbreviation);
     }
-    if (option.negatable) {
+    if (option.negatable != null && option.negatable!) {
       knownOptions.add('no-$name');
     }
   });
@@ -331,10 +325,7 @@
 
 /// Convert a [source] string to a Uri, where the source may be a
 /// dart/file/package URI or a local win/mac/linux path.
-///
-/// If [source] is null, this will return null.
-Uri sourcePathToUri(String source, {bool windows}) {
-  if (source == null) return null;
+Uri sourcePathToUri(String source, {bool? windows}) {
   if (windows == null) {
     // Running on the web the Platform check will fail, and we can't use
     // fromEnvironment because internally it's set to true for dart.library.io.
@@ -358,7 +349,7 @@
   return result;
 }
 
-Uri sourcePathToRelativeUri(String source, {bool windows}) {
+Uri sourcePathToRelativeUri(String source, {bool? windows}) {
   var uri = sourcePathToUri(source, windows: windows);
   if (uri.isScheme('file')) {
     var uriPath = uri.path;
@@ -388,8 +379,8 @@
 // TODO(#40251): Remove this logic from dev_compiler itself, push it to the
 // invokers of dev_compiler which have more knowledge about how they want
 // source paths to look.
-Map placeSourceMap(Map sourceMap, String sourceMapPath, String multiRootScheme,
-    {String multiRootOutputPath, String sourceMapBase}) {
+Map placeSourceMap(Map sourceMap, String sourceMapPath, String? multiRootScheme,
+    {String? multiRootOutputPath, String? sourceMapBase}) {
   var map = Map.from(sourceMap);
   // Convert to a local file path if it's not.
   sourceMapPath = sourcePathToUri(p.absolute(p.fromUri(sourceMapPath))).path;
@@ -446,7 +437,7 @@
   /// Optionally provides the front_end state from the previous compilation,
   /// which can be passed to [compile] to potentially speed up the next
   /// compilation.
-  final InitializedCompilerState kernelState;
+  final InitializedCompilerState? kernelState;
 
   /// The process exit code of the compiler.
   final int exitCode;
@@ -454,7 +445,7 @@
   CompilerResult(this.exitCode, {this.kernelState});
 
   /// Gets the kernel compiler state, if any.
-  Object get compilerState => kernelState;
+  Object? get compilerState => kernelState;
 
   /// Whether the program compiled without any fatal errors (equivalent to
   /// [exitCode] == 0).
diff --git a/pkg/dev_compiler/lib/src/kernel/command.dart b/pkg/dev_compiler/lib/src/kernel/command.dart
index 7e981bd..713f095 100644
--- a/pkg/dev_compiler/lib/src/kernel/command.dart
+++ b/pkg/dev_compiler/lib/src/kernel/command.dart
@@ -276,7 +276,7 @@
         compileSdk,
         sourcePathToUri(getSdkPath()),
         compileSdk ? null : sourcePathToUri(sdkSummaryPath),
-        sourcePathToUri(packageFile),
+        packageFile != null ? sourcePathToUri(packageFile) : null,
         sourcePathToUri(librarySpecPath),
         additionalDills,
         DevCompilerTarget(TargetFlags(
@@ -314,7 +314,7 @@
         compileSdk,
         sourcePathToUri(getSdkPath()),
         compileSdk ? null : sourcePathToUri(sdkSummaryPath),
-        sourcePathToUri(packageFile),
+        packageFile != null ? sourcePathToUri(packageFile) : null,
         sourcePathToUri(librarySpecPath),
         additionalDills,
         inputDigests,
diff --git a/pkg/front_end/lib/src/api_unstable/ddc.dart b/pkg/front_end/lib/src/api_unstable/ddc.dart
index 2ebfdf1..d462d69 100644
--- a/pkg/front_end/lib/src/api_unstable/ddc.dart
+++ b/pkg/front_end/lib/src/api_unstable/ddc.dart
@@ -113,10 +113,10 @@
 InitializedCompilerState initializeCompiler(
     InitializedCompilerState? oldState,
     bool compileSdk,
-    Uri sdkRoot,
-    Uri sdkSummary,
-    Uri packagesFile,
-    Uri librariesSpecificationUri,
+    Uri? sdkRoot,
+    Uri? sdkSummary,
+    Uri? packagesFile,
+    Uri? librariesSpecificationUri,
     List<Uri> additionalDills,
     Target target,
     {FileSystem? fileSystem,
@@ -166,14 +166,14 @@
 /// Re-uses cached components from [oldState.workerInputCache], and reloads them
 /// as necessary based on [workerInputDigests].
 Future<InitializedCompilerState> initializeIncrementalCompiler(
-    InitializedCompilerState oldState,
+    InitializedCompilerState? oldState,
     Set<String> tags,
     List<Component> doneAdditionalDills,
     bool compileSdk,
-    Uri sdkRoot,
-    Uri sdkSummary,
-    Uri packagesFile,
-    Uri librariesSpecificationUri,
+    Uri? sdkRoot,
+    Uri? sdkSummary,
+    Uri? packagesFile,
+    Uri? librariesSpecificationUri,
     List<Uri> additionalDills,
     Map<Uri, List<int>> workerInputDigests,
     Target target,
diff --git a/runtime/vm/compiler/assembler/assembler_riscv_test.cc b/runtime/vm/compiler/assembler/assembler_riscv_test.cc
index 34c1060..ff2bfb3 100644
--- a/runtime/vm/compiler/assembler/assembler_riscv_test.cc
+++ b/runtime/vm/compiler/assembler/assembler_riscv_test.cc
@@ -3369,11 +3369,31 @@
   EXPECT_EQ(-3.0f, CallF(test->entry(), -3.0f, -3.0f));
   EXPECT_EQ(-5.0f, CallF(test->entry(), -3.0f, -5.0f));
 
+  EXPECT_EQ(bit_cast<uint32_t>(-0.0f),
+            bit_cast<uint32_t>(CallF(test->entry(), 0.0f, -0.0f)));
+  EXPECT_EQ(bit_cast<uint32_t>(-0.0f),
+            bit_cast<uint32_t>(CallF(test->entry(), -0.0f, 0.0f)));
+
   float qNAN = std::numeric_limits<float>::quiet_NaN();
   EXPECT_EQ(3.0f, CallF(test->entry(), 3.0f, qNAN));
   EXPECT_EQ(3.0f, CallF(test->entry(), qNAN, 3.0f));
   EXPECT_EQ(-3.0f, CallF(test->entry(), -3.0f, qNAN));
   EXPECT_EQ(-3.0f, CallF(test->entry(), qNAN, -3.0f));
+
+  float sNAN = std::numeric_limits<float>::signaling_NaN();
+  EXPECT_EQ(3.0f, CallF(test->entry(), 3.0f, sNAN));
+  EXPECT_EQ(3.0f, CallF(test->entry(), sNAN, 3.0f));
+  EXPECT_EQ(-3.0f, CallF(test->entry(), -3.0f, sNAN));
+  EXPECT_EQ(-3.0f, CallF(test->entry(), sNAN, -3.0f));
+
+  EXPECT_EQ(bit_cast<uint32_t>(qNAN),
+            bit_cast<uint32_t>(CallF(test->entry(), qNAN, qNAN)));
+  EXPECT_EQ(bit_cast<uint32_t>(qNAN),
+            bit_cast<uint32_t>(CallF(test->entry(), sNAN, sNAN)));
+  EXPECT_EQ(bit_cast<uint32_t>(qNAN),
+            bit_cast<uint32_t>(CallF(test->entry(), qNAN, sNAN)));
+  EXPECT_EQ(bit_cast<uint32_t>(qNAN),
+            bit_cast<uint32_t>(CallF(test->entry(), sNAN, qNAN)));
 }
 
 ASSEMBLER_TEST_GENERATE(SingleMax, assembler) {
@@ -3399,11 +3419,31 @@
   EXPECT_EQ(-3.0f, CallF(test->entry(), -3.0f, -3.0f));
   EXPECT_EQ(-3.0f, CallF(test->entry(), -3.0f, -5.0f));
 
+  EXPECT_EQ(bit_cast<uint32_t>(0.0f),
+            bit_cast<uint32_t>(CallF(test->entry(), 0.0f, -0.0f)));
+  EXPECT_EQ(bit_cast<uint32_t>(0.0f),
+            bit_cast<uint32_t>(CallF(test->entry(), -0.0f, 0.0f)));
+
   float qNAN = std::numeric_limits<float>::quiet_NaN();
   EXPECT_EQ(3.0f, CallF(test->entry(), 3.0f, qNAN));
   EXPECT_EQ(3.0f, CallF(test->entry(), qNAN, 3.0f));
   EXPECT_EQ(-3.0f, CallF(test->entry(), -3.0f, qNAN));
   EXPECT_EQ(-3.0f, CallF(test->entry(), qNAN, -3.0f));
+
+  float sNAN = std::numeric_limits<float>::signaling_NaN();
+  EXPECT_EQ(3.0f, CallF(test->entry(), 3.0f, sNAN));
+  EXPECT_EQ(3.0f, CallF(test->entry(), sNAN, 3.0f));
+  EXPECT_EQ(-3.0f, CallF(test->entry(), -3.0f, sNAN));
+  EXPECT_EQ(-3.0f, CallF(test->entry(), sNAN, -3.0f));
+
+  EXPECT_EQ(bit_cast<uint32_t>(qNAN),
+            bit_cast<uint32_t>(CallF(test->entry(), qNAN, qNAN)));
+  EXPECT_EQ(bit_cast<uint32_t>(qNAN),
+            bit_cast<uint32_t>(CallF(test->entry(), sNAN, sNAN)));
+  EXPECT_EQ(bit_cast<uint32_t>(qNAN),
+            bit_cast<uint32_t>(CallF(test->entry(), qNAN, sNAN)));
+  EXPECT_EQ(bit_cast<uint32_t>(qNAN),
+            bit_cast<uint32_t>(CallF(test->entry(), sNAN, qNAN)));
 }
 
 ASSEMBLER_TEST_GENERATE(SingleEqual, assembler) {
@@ -4312,11 +4352,31 @@
   EXPECT_EQ(-3.0, CallD(test->entry(), -3.0, -3.0));
   EXPECT_EQ(-5.0, CallD(test->entry(), -3.0, -5.0));
 
+  EXPECT_EQ(bit_cast<uint64_t>(-0.0),
+            bit_cast<uint64_t>(CallD(test->entry(), 0.0, -0.0)));
+  EXPECT_EQ(bit_cast<uint64_t>(-0.0),
+            bit_cast<uint64_t>(CallD(test->entry(), -0.0, 0.0)));
+
   double qNAN = std::numeric_limits<double>::quiet_NaN();
   EXPECT_EQ(3.0, CallD(test->entry(), 3.0, qNAN));
   EXPECT_EQ(3.0, CallD(test->entry(), qNAN, 3.0));
   EXPECT_EQ(-3.0, CallD(test->entry(), -3.0, qNAN));
   EXPECT_EQ(-3.0, CallD(test->entry(), qNAN, -3.0));
+
+  double sNAN = std::numeric_limits<double>::signaling_NaN();
+  EXPECT_EQ(3.0, CallD(test->entry(), 3.0, sNAN));
+  EXPECT_EQ(3.0, CallD(test->entry(), sNAN, 3.0));
+  EXPECT_EQ(-3.0, CallD(test->entry(), -3.0, sNAN));
+  EXPECT_EQ(-3.0, CallD(test->entry(), sNAN, -3.0));
+
+  EXPECT_EQ(bit_cast<uint64_t>(qNAN),
+            bit_cast<uint64_t>(CallD(test->entry(), sNAN, sNAN)));
+  EXPECT_EQ(bit_cast<uint64_t>(qNAN),
+            bit_cast<uint64_t>(CallD(test->entry(), qNAN, qNAN)));
+  EXPECT_EQ(bit_cast<uint64_t>(qNAN),
+            bit_cast<uint64_t>(CallD(test->entry(), qNAN, sNAN)));
+  EXPECT_EQ(bit_cast<uint64_t>(qNAN),
+            bit_cast<uint64_t>(CallD(test->entry(), sNAN, qNAN)));
 }
 
 ASSEMBLER_TEST_GENERATE(DoubleMax, assembler) {
@@ -4342,11 +4402,31 @@
   EXPECT_EQ(-3.0, CallD(test->entry(), -3.0, -3.0));
   EXPECT_EQ(-3.0, CallD(test->entry(), -3.0, -5.0));
 
+  EXPECT_EQ(bit_cast<uint64_t>(0.0),
+            bit_cast<uint64_t>(CallD(test->entry(), 0.0, -0.0)));
+  EXPECT_EQ(bit_cast<uint64_t>(0.0),
+            bit_cast<uint64_t>(CallD(test->entry(), -0.0, 0.0)));
+
   double qNAN = std::numeric_limits<double>::quiet_NaN();
   EXPECT_EQ(3.0, CallD(test->entry(), 3.0, qNAN));
   EXPECT_EQ(3.0, CallD(test->entry(), qNAN, 3.0));
   EXPECT_EQ(-3.0, CallD(test->entry(), -3.0, qNAN));
   EXPECT_EQ(-3.0, CallD(test->entry(), qNAN, -3.0));
+
+  double sNAN = std::numeric_limits<double>::signaling_NaN();
+  EXPECT_EQ(3.0, CallD(test->entry(), 3.0, sNAN));
+  EXPECT_EQ(3.0, CallD(test->entry(), sNAN, 3.0));
+  EXPECT_EQ(-3.0, CallD(test->entry(), -3.0, sNAN));
+  EXPECT_EQ(-3.0, CallD(test->entry(), sNAN, -3.0));
+
+  EXPECT_EQ(bit_cast<uint64_t>(qNAN),
+            bit_cast<uint64_t>(CallD(test->entry(), sNAN, sNAN)));
+  EXPECT_EQ(bit_cast<uint64_t>(qNAN),
+            bit_cast<uint64_t>(CallD(test->entry(), qNAN, qNAN)));
+  EXPECT_EQ(bit_cast<uint64_t>(qNAN),
+            bit_cast<uint64_t>(CallD(test->entry(), qNAN, sNAN)));
+  EXPECT_EQ(bit_cast<uint64_t>(qNAN),
+            bit_cast<uint64_t>(CallD(test->entry(), sNAN, qNAN)));
 }
 
 ASSEMBLER_TEST_GENERATE(DoubleToSingle, assembler) {
diff --git a/runtime/vm/heap/heap.cc b/runtime/vm/heap/heap.cc
index ed01f6a..8b6c799 100644
--- a/runtime/vm/heap/heap.cc
+++ b/runtime/vm/heap/heap.cc
@@ -485,6 +485,11 @@
   }
   {
     GcSafepointOperationScope safepoint_operation(thread);
+    if (reason == GCReason::kFinalize &&
+        old_space_.phase() != PageSpace::kAwaitingFinalization) {
+      return;  // Lost race.
+    }
+
     thread->isolate_group()->ForEachIsolate(
         [&](Isolate* isolate) {
           // Discard regexp backtracking stacks to further reduce memory usage.
diff --git a/runtime/vm/malloc_hooks_ia32.cc b/runtime/vm/malloc_hooks_ia32.cc
index 0572ac0..2b7a371 100644
--- a/runtime/vm/malloc_hooks_ia32.cc
+++ b/runtime/vm/malloc_hooks_ia32.cc
@@ -11,9 +11,9 @@
 namespace dart {
 
 #if defined(DEBUG)
-const intptr_t kSkipCount = 7;
-#elif !(defined(PRODUCT) || defined(DEBUG))
 const intptr_t kSkipCount = 6;
+#elif !(defined(PRODUCT) || defined(DEBUG))
+const intptr_t kSkipCount = 5;
 #endif
 
 }  // namespace dart
diff --git a/runtime/vm/simulator_riscv.cc b/runtime/vm/simulator_riscv.cc
index 2229c31..3b2034f 100644
--- a/runtime/vm/simulator_riscv.cc
+++ b/runtime/vm/simulator_riscv.cc
@@ -1997,6 +1997,42 @@
   pc_ += instr.length();
 }
 
+// "For the purposes of these instructions only, the value −0.0 is considered to
+//  be less than the value +0.0. If both inputs are NaNs, the result is the
+//  canonical NaN. If only one operand is a NaN, the result is the non-NaN
+//  operand."
+static double rv_fmin(double x, double y) {
+  if (isnan(x) && isnan(y)) return std::numeric_limits<double>::quiet_NaN();
+  if (isnan(x)) return y;
+  if (isnan(y)) return x;
+  if (x == y) return signbit(x) ? x : y;
+  return fmin(x, y);
+}
+
+static double rv_fmax(double x, double y) {
+  if (isnan(x) && isnan(y)) return std::numeric_limits<double>::quiet_NaN();
+  if (isnan(x)) return y;
+  if (isnan(y)) return x;
+  if (x == y) return signbit(x) ? y : x;
+  return fmax(x, y);
+}
+
+static float rv_fminf(float x, float y) {
+  if (isnan(x) && isnan(y)) return std::numeric_limits<float>::quiet_NaN();
+  if (isnan(x)) return y;
+  if (isnan(y)) return x;
+  if (x == y) return signbit(x) ? x : y;
+  return fminf(x, y);
+}
+
+static float rv_fmaxf(float x, float y) {
+  if (isnan(x) && isnan(y)) return std::numeric_limits<float>::quiet_NaN();
+  if (isnan(x)) return y;
+  if (isnan(y)) return x;
+  if (x == y) return signbit(x) ? y : x;
+  return fmaxf(x, y);
+}
+
 static bool is_quiet(float x) {
   // Warning: This is true on Intel/ARM, but not everywhere.
   return (bit_cast<uint32_t>(x) & (static_cast<uint32_t>(1) << 22)) != 0;
@@ -2258,10 +2294,10 @@
       float rs2 = get_fregs(instr.frs2());
       switch (instr.funct3()) {
         case MIN:
-          set_fregs(instr.frd(), fminf(rs1, rs2));
+          set_fregs(instr.frd(), rv_fminf(rs1, rs2));
           break;
         case MAX:
-          set_fregs(instr.frd(), fmaxf(rs1, rs2));
+          set_fregs(instr.frd(), rv_fmaxf(rs1, rs2));
           break;
         default:
           IllegalInstruction(instr);
@@ -2412,10 +2448,10 @@
       double rs2 = get_fregd(instr.frs2());
       switch (instr.funct3()) {
         case MIN:
-          set_fregd(instr.frd(), fmin(rs1, rs2));
+          set_fregd(instr.frd(), rv_fmin(rs1, rs2));
           break;
         case MAX:
-          set_fregd(instr.frd(), fmax(rs1, rs2));
+          set_fregd(instr.frd(), rv_fmax(rs1, rs2));
           break;
         default:
           IllegalInstruction(instr);
diff --git a/tools/VERSION b/tools/VERSION
index 122e79d..43b8d36 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 215
+PRERELEASE 216
 PRERELEASE_PATCH 0
\ No newline at end of file