Version 2.9.0-1.0.dev

Merge commit 'ae0eca83188ce3bd99e3f012cf7eec4b0bdf2c4f' into dev
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 4cbffad..60c449a 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -44,7 +44,7 @@
     runtime_lib = 'lib/_internal/js_dev_runtime/private/ddc_runtime/'
     for nnbd_file in files:
         if nnbd_file.startswith('sdk_nnbd/' + runtime_lib):
-            file = 'sdk/' + runtime_lib + nnbd_file[4:]
+            file = 'sdk/' + nnbd_file[9:]
             if not file in files:
                 unsynchronized_files.append(file)
     if unsynchronized_files:
diff --git a/pkg/analysis_server/lib/src/edit/fix/dartfix_info.dart b/pkg/analysis_server/lib/src/edit/fix/dartfix_info.dart
index 23ca3f4..80f3f5a 100644
--- a/pkg/analysis_server/lib/src/edit/fix/dartfix_info.dart
+++ b/pkg/analysis_server/lib/src/edit/fix/dartfix_info.dart
@@ -63,6 +63,9 @@
   LintFixInfo.preferAdjacentStringConcatenation,
   LintFixInfo.preferCollectionLiterals,
   LintFixInfo.preferConditionalAssignment,
+  LintFixInfo.preferConstConstructors,
+  LintFixInfo.preferConstConstructorsInImmutables,
+  LintFixInfo.preferConstDeclarations,
   LintFixInfo.preferContains,
   LintFixInfo.preferEqualForDefaultValues,
   LintFixInfo.preferFinalFields,
@@ -322,6 +325,24 @@
     isPedantic: true,
   );
 
+  static final preferConstConstructors = LintFixInfo(
+    'prefer_const_constructors',
+    DartFixKind.ADD_CONST,
+    'Make the instantiation const.',
+  );
+
+  static final preferConstConstructorsInImmutables = LintFixInfo(
+    'prefer_const_constructors_in_immutables',
+    DartFixKind.ADD_CONST,
+    'Make the constructor const.',
+  );
+
+  static final preferConstDeclarations = LintFixInfo(
+    'prefer_const_declarations',
+    DartFixKind.REPLACE_FINAL_WITH_CONST,
+    'Make the declaration const.',
+  );
+
   static final preferContains = LintFixInfo(
     'prefer_contains',
     DartFixKind.CONVERT_TO_CONTAINS,
@@ -423,7 +444,7 @@
   static final preferSingleQuotes = LintFixInfo(
     'prefer_single_quotes',
     DartFixKind.CONVERT_TO_SINGLE_QUOTED_STRING,
-    'Convert strings using a dobule quote to use a single quote.',
+    'Convert strings using a double quote to use a single quote.',
     isPedantic: true,
   );
 
diff --git a/pkg/analysis_server/lib/src/edit/fix/non_nullable_fix.dart b/pkg/analysis_server/lib/src/edit/fix/non_nullable_fix.dart
index 35c6e54..3a695592 100644
--- a/pkg/analysis_server/lib/src/edit/fix/non_nullable_fix.dart
+++ b/pkg/analysis_server/lib/src/edit/fix/non_nullable_fix.dart
@@ -12,11 +12,11 @@
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/dart/analysis/experiments.dart';
 import 'package:analyzer/src/generated/source.dart';
-import 'package:analyzer/src/task/options.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
+import 'package:charcode/charcode.dart';
 import 'package:nnbd_migration/nnbd_migration.dart';
+import 'package:pub_semver/pub_semver.dart';
 import 'package:source_span/source_span.dart';
 import 'package:yaml/yaml.dart';
 
@@ -28,6 +28,13 @@
   /// mature enough.
   static const bool _usePermissiveMode = true;
 
+  // TODO(srawlins): Refactor to use
+  //  `Feature.non_nullable.firstSupportedVersion` when this becomes non-null.
+  static const String _intendedMinimumSdkVersion = '2.8.0';
+
+  static const String _intendedSdkVersionConstraint =
+      '>=$_intendedMinimumSdkVersion <3.0.0';
+
   final int preferredPort;
 
   final DartFixListener listener;
@@ -96,101 +103,102 @@
     }
   }
 
-  /// If the package contains an analysis_options.yaml file, then update the
-  /// file to enabled NNBD. If that file does not exist, but the package
-  /// contains a pubspec.yaml, then create the analysis_options.yaml file.
+  /// Update the pubspec.yaml file to specify a minimum Dart SDK version which
+  /// enables the Null Safety feature.
   @override
   Future<void> processPackage(Folder pkgFolder) async {
     if (!_packageIsNNBD) {
       return;
     }
 
-    // TODO(danrubel): Update pubspec.yaml to enable NNBD
-
-    var optionsFile = pkgFolder.getChildAssumingFile('analysis_options.yaml');
-    String optionsContent;
-    YamlNode optionsMap;
-    if (optionsFile.exists) {
-      try {
-        optionsContent = optionsFile.readAsStringSync();
-      } on FileSystemException catch (e) {
-        processYamlException('read', optionsFile.path, e);
-        return;
-      }
-      try {
-        optionsMap = loadYaml(optionsContent);
-      } on YamlException catch (e) {
-        processYamlException('parse', optionsFile.path, e);
-        return;
-      }
+    var pubspecFile = pkgFolder.getChildAssumingFile('pubspec.yaml');
+    String pubspecContent;
+    YamlNode pubspecMap;
+    if (!pubspecFile.exists) {
+      // TODO(srawlins): Handle other package types, such as Bazel.
+      return;
     }
 
-    SourceSpan parentSpan;
-    String content;
-    YamlNode analyzerOptions;
-    if (optionsMap is YamlMap) {
-      analyzerOptions = optionsMap.nodes[AnalyzerOptions.analyzer];
+    try {
+      pubspecContent = pubspecFile.readAsStringSync();
+    } on FileSystemException catch (e) {
+      processYamlException('read', pubspecFile.path, e);
+      return;
     }
-    if (analyzerOptions == null) {
-      var start = SourceLocation(0, line: 0, column: 0);
-      parentSpan = SourceSpan(start, start, '');
-      content = '''
-analyzer:
-  enable-experiment:
-    - non-nullable
-
-''';
-    } else if (analyzerOptions is YamlMap) {
-      var experiments = analyzerOptions.nodes[AnalyzerOptions.enableExperiment];
-      if (experiments == null) {
-        parentSpan = analyzerOptions.span;
-        content = '''
-
-  enable-experiment:
-    - non-nullable''';
-      } else if (experiments is YamlList) {
-        experiments.nodes.firstWhere(
-          (node) => node.span.text == EnableString.non_nullable,
-          orElse: () {
-            parentSpan = experiments.span;
-            content = '''
-
-    - non-nullable''';
-            return null;
-          },
-        );
-      }
+    try {
+      pubspecMap = loadYaml(pubspecContent);
+    } on YamlException catch (e) {
+      processYamlException('parse', pubspecFile.path, e);
+      return;
     }
 
-    if (parentSpan != null) {
-      final space = ' '.codeUnitAt(0);
-      final cr = '\r'.codeUnitAt(0);
-      final lf = '\n'.codeUnitAt(0);
-
+    /// Inserts [content] into [pubspecFile], immediately after [parentSpan].
+    void insertAfterParent(SourceSpan parentSpan, String content) {
       var line = parentSpan.end.line;
       var offset = parentSpan.end.offset;
+      // Walk [offset] and [line] back to the first non-whitespace character
+      // before [offset].
       while (offset > 0) {
-        var ch = optionsContent.codeUnitAt(offset - 1);
-        if (ch == space || ch == cr) {
+        var ch = pubspecContent.codeUnitAt(offset - 1);
+        if (ch == $space || ch == $cr) {
           --offset;
-        } else if (ch == lf) {
+        } else if (ch == $lf) {
           --offset;
           --line;
         } else {
           break;
         }
       }
+      var edit = SourceEdit(offset, 0, content);
       listener.addSourceFileEdit(
-          'enable non-nullable analysis',
-          Location(
-            optionsFile.path,
-            offset,
-            content.length,
-            line,
-            0,
-          ),
-          SourceFileEdit(optionsFile.path, 0,
-              edits: [SourceEdit(offset, 0, content)]));
+          'enable Null Safety language feature',
+          Location(pubspecFile.path, offset, content.length, line, 0),
+          SourceFileEdit(pubspecFile.path, 0, edits: [edit]));
+    }
+
+    void replaceSpan(SourceSpan span, String content) {
+      var line = span.start.line;
+      var offset = span.start.offset;
+      var edit = SourceEdit(offset, span.length, content);
+      listener.addSourceFileEdit(
+          'enable Null Safety language feature',
+          Location(pubspecFile.path, offset, content.length, line, 0),
+          SourceFileEdit(pubspecFile.path, 0, edits: [edit]));
+    }
+
+    YamlNode environmentOptions;
+    if (pubspecMap is YamlMap) {
+      environmentOptions = pubspecMap.nodes['environment'];
+    }
+    if (environmentOptions == null) {
+      var start = SourceLocation(0, line: 0, column: 0);
+      var content = '''
+environment:
+  sdk: '$_intendedSdkVersionConstraint'
+
+''';
+      insertAfterParent(SourceSpan(start, start, ''), content);
+    } else if (environmentOptions is YamlMap) {
+      var sdk = environmentOptions.nodes['sdk'];
+      if (sdk == null) {
+        var content = """
+
+  sdk: '$_intendedSdkVersionConstraint'""";
+        insertAfterParent(environmentOptions.span, content);
+      } else if (sdk is YamlScalar) {
+        var currentConstraint = VersionConstraint.parse(sdk.value);
+        var minimumVersion = Version.parse('2.8.0');
+        if (currentConstraint is VersionRange &&
+            currentConstraint.min >= minimumVersion) {
+          // The current SDK version constraint already enables Null Safety.
+          return;
+        } else {
+          // TODO(srawlins): This overwrites the current maximum version. In
+          // the uncommon situation that the maximum is not '<3.0.0', it should
+          // not.
+          replaceSpan(sdk.span, "'$_intendedSdkVersionConstraint'");
+        }
+      }
     }
   }
 
@@ -219,11 +227,11 @@
   $optionsFilePath
   $error
 
-  Manually update this file to enable non-nullable by adding:
+  Manually update this file to enable the Null Safety language feature by
+  adding:
 
-    analyzer:
-      enable-experiment:
-        - non-nullable
+    environment:
+      sdk: '$_intendedSdkVersionConstraint';
 ''');
     _packageIsNNBD = false;
   }
diff --git a/pkg/analysis_server/lib/src/edit/nnbd_migration/info_builder.dart b/pkg/analysis_server/lib/src/edit/nnbd_migration/info_builder.dart
index 4c1bbe1..692059e 100644
--- a/pkg/analysis_server/lib/src/edit/nnbd_migration/info_builder.dart
+++ b/pkg/analysis_server/lib/src/edit/nnbd_migration/info_builder.dart
@@ -125,10 +125,26 @@
   }
 
   /// Return an edit that can be applied.
-  List<EditDetail> _computeEdits(AtomicEditInfo fixInfo, int offset) {
+  List<EditDetail> _computeEdits(
+      AtomicEditInfo fixInfo, int offset, String content) {
+    EditDetail _removeHint(String description) => EditDetail.fromSourceEdit(
+        description,
+        fixInfo.hintComment.changesToRemove(content).toSourceEdits().single);
+
+    EditDetail _changeHint(String description, String replacement) =>
+        EditDetail.fromSourceEdit(
+            description,
+            fixInfo.hintComment
+                .changesToReplace(content, replacement)
+                .toSourceEdits()
+                .single);
+
     var edits = <EditDetail>[];
     var fixKind = fixInfo.description.kind;
     switch (fixKind) {
+      case NullabilityFixKind.addLateDueToHint:
+        edits.add(_removeHint('Remove /*late*/ hint'));
+        break;
       case NullabilityFixKind.addRequired:
         // TODO(brianwilkerson) This doesn't verify that the meta package has
         //  been imported.
@@ -141,7 +157,7 @@
         edits.add(EditDetail('Add /*!*/ hint', offset, 0, '/*!*/'));
         break;
       case NullabilityFixKind.checkExpressionDueToHint:
-        edits.add(EditDetail('Remove /*!*/ hint', offset, 5, ''));
+        edits.add(_removeHint('Remove /*!*/ hint'));
         break;
       case NullabilityFixKind.downcastExpression:
       case NullabilityFixKind.otherCastExpression:
@@ -162,12 +178,12 @@
         edits.add(EditDetail('Add /*?*/ hint', offset, 0, '/*?*/'));
         break;
       case NullabilityFixKind.makeTypeNullableDueToHint:
-        edits.add(EditDetail('Add /*!*/ hint', offset, 5, '/*!*/'));
-        edits.add(EditDetail('Remove /*?*/ hint', offset, 5, ''));
+        edits.add(_changeHint('Change to /*!*/ hint', '/*!*/'));
+        edits.add(_removeHint('Remove /*?*/ hint'));
         break;
       case NullabilityFixKind.typeNotMadeNullableDueToHint:
-        edits.add(EditDetail('Remove /*!*/ hint', offset, 5, ''));
-        edits.add(EditDetail('Add /*?*/ hint', offset, 5, '/*?*/'));
+        edits.add(_removeHint('Remove /*!*/ hint'));
+        edits.add(_changeHint('Change to /*?*/ hint', '/*?*/'));
         break;
     }
     return edits;
@@ -275,6 +291,7 @@
     var regions = unitInfo.regions;
     var lineInfo = result.unit.lineInfo;
     var insertions = <int, List<AtomicEdit>>{};
+    var hintsSeen = <HintComment>{};
 
     // Apply edits and build the regions.
     var changes = sourceInfo.changes ?? {};
@@ -296,31 +313,35 @@
           (insertions[sourceOffset] ??= []).add(AtomicEdit.insert(replacement));
         }
         var info = edit.info;
-        var edits = info != null ? _computeEdits(info, sourceOffset) : [];
+        var edits = info != null
+            ? _computeEdits(info, sourceOffset, result.content)
+            : [];
         var lineNumber = lineInfo.getLocation(sourceOffset).lineNumber;
         var traces = info == null ? const [] : _computeTraces(info.fixReasons);
         var description = info?.description;
+        var hint = info?.hintComment;
+        var isCounted = hint == null || hintsSeen.add(hint);
         if (description != null) {
           var explanation = description.appliedMessage;
           var kind = description.kind;
           if (edit.isInformative) {
             regions.add(RegionInfo(RegionType.informative, offset,
-                replacement.length, lineNumber, explanation, kind,
+                replacement.length, lineNumber, explanation, kind, isCounted,
                 edits: edits, traces: traces));
           } else if (edit.isInsertion) {
             regions.add(RegionInfo(RegionType.add, offset, replacement.length,
-                lineNumber, explanation, kind,
+                lineNumber, explanation, kind, isCounted,
                 edits: edits, traces: traces));
           } else if (edit.isDeletion) {
             regions.add(RegionInfo(RegionType.remove, offset, length,
-                lineNumber, explanation, kind,
+                lineNumber, explanation, kind, isCounted,
                 edits: edits, traces: traces));
           } else if (edit.isReplacement) {
             regions.add(RegionInfo(RegionType.remove, offset, length,
-                lineNumber, explanation, kind,
+                lineNumber, explanation, kind, isCounted,
                 edits: edits, traces: traces));
             regions.add(RegionInfo(RegionType.add, end, replacement.length,
-                lineNumber, explanation, kind,
+                lineNumber, explanation, kind, isCounted,
                 edits: edits, traces: traces));
           } else {
             throw StateError(
diff --git a/pkg/analysis_server/lib/src/edit/nnbd_migration/migration_info.dart b/pkg/analysis_server/lib/src/edit/nnbd_migration/migration_info.dart
index bde42b0..e563269 100644
--- a/pkg/analysis_server/lib/src/edit/nnbd_migration/migration_info.dart
+++ b/pkg/analysis_server/lib/src/edit/nnbd_migration/migration_info.dart
@@ -6,6 +6,7 @@
 import 'package:analysis_server/src/edit/nnbd_migration/unit_link.dart';
 import 'package:analysis_server/src/edit/preview/preview_site.dart';
 import 'package:analyzer/src/generated/utilities_general.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:collection/collection.dart';
 import 'package:crypto/crypto.dart';
 import 'package:meta/meta.dart';
@@ -29,6 +30,12 @@
 
   /// Initialize a newly created detail.
   EditDetail(this.description, this.offset, this.length, this.replacement);
+
+  /// Initializes a detail based on a [SourceEdit] object.
+  factory EditDetail.fromSourceEdit(
+          String description, SourceEdit sourceEdit) =>
+      EditDetail(description, sourceEdit.offset, sourceEdit.length,
+          sourceEdit.replacement);
 }
 
 /// A class storing rendering information for an entire migration report.
@@ -150,6 +157,9 @@
   /// The kind of fix that was applied.
   final NullabilityFixKind kind;
 
+  /// Indicates whether this region should be counted in the edit summary.
+  final bool isCounted;
+
   /// A list of the edits that are related to this range.
   List<EditDetail> edits;
 
@@ -159,7 +169,7 @@
 
   /// Initialize a newly created region.
   RegionInfo(this.regionType, this.offset, this.length, this.lineNumber,
-      this.explanation, this.kind,
+      this.explanation, this.kind, this.isCounted,
       {this.edits = const [], this.traces = const []});
 }
 
@@ -277,9 +287,16 @@
           return region;
         }
         // TODO: adjust traces
-        return RegionInfo(region.regionType, region.offset + length,
-            region.length, region.lineNumber, region.explanation, region.kind,
-            edits: region.edits, traces: region.traces);
+        return RegionInfo(
+            region.regionType,
+            region.offset + length,
+            region.length,
+            region.lineNumber,
+            region.explanation,
+            region.kind,
+            region.isCounted,
+            edits: region.edits,
+            traces: region.traces);
       }));
 
       diskChangesOffsetMapper = OffsetMapper.sequence(
diff --git a/pkg/analysis_server/lib/src/edit/nnbd_migration/unit_renderer.dart b/pkg/analysis_server/lib/src/edit/nnbd_migration/unit_renderer.dart
index aabfdd8..4ec259f 100644
--- a/pkg/analysis_server/lib/src/edit/nnbd_migration/unit_renderer.dart
+++ b/pkg/analysis_server/lib/src/edit/nnbd_migration/unit_renderer.dart
@@ -32,6 +32,7 @@
     NullabilityFixKind.addType,
     NullabilityFixKind.replaceVar,
     NullabilityFixKind.removeAs,
+    NullabilityFixKind.addLateDueToHint,
     NullabilityFixKind.checkExpressionDueToHint,
     NullabilityFixKind.makeTypeNullableDueToHint,
     NullabilityFixKind.removeLanguageVersionComment
@@ -68,7 +69,7 @@
     var editListsByKind = <NullabilityFixKind, List<EditListItem>>{};
     for (var region in unitInfo.fixRegions) {
       var kind = region.kind;
-      if (kind != null) {
+      if (kind != null && region.isCounted) {
         (editListsByKind[kind] ??= []).add(EditListItem(
             line: region.lineNumber,
             explanation: region.explanation,
@@ -240,6 +241,8 @@
   String _headerForKind(NullabilityFixKind kind, int count) {
     var s = count == 1 ? '' : 's';
     switch (kind) {
+      case NullabilityFixKind.addLateDueToHint:
+        return '$count late hint$s converted to late keyword$s';
       case NullabilityFixKind.addRequired:
         return '$count required keyword$s added';
       case NullabilityFixKind.downcastExpression:
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart b/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
index 10c24e1..7fd4bc0 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
@@ -4,6 +4,11 @@
 
 /// Utility methods to compute the value of the features used for code
 /// completion.
+import 'dart:math' as math;
+
+import 'package:analysis_server/src/protocol_server.dart'
+    show convertElementToElementKind;
+import 'package:analysis_server/src/services/completion/dart/relevance_tables.g.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart'
@@ -89,6 +94,29 @@
     }
   }
 
+  /// Return the value of the _element kind_ feature for the [element] when
+  /// completing at the given [completionLocation]. If a [distance] is given it
+  /// will be used to provide finer-grained relevance scores.
+  double elementKindFeature(Element element, String completionLocation,
+      {int distance}) {
+    if (completionLocation == null) {
+      return -1.0;
+    }
+    var locationTable = elementKindRelevance[completionLocation];
+    if (locationTable == null) {
+      return -1.0;
+    }
+    var kind = convertElementToElementKind(element);
+    var range = locationTable[kind];
+    if (range == null) {
+      return 0.0;
+    }
+    if (distance == null) {
+      return range.upper;
+    }
+    return range.conditionalProbability(_distanceToPercent(distance));
+  }
+
   /// Return the value of the _has deprecated_ feature for the given [element].
   double hasDeprecatedFeature(Element element) {
     return element.hasDeprecated ? 0.0 : 1.0;
@@ -115,7 +143,7 @@
     if (distance < 0) {
       return 0.0;
     }
-    return 1.0 / (distance + 1);
+    return _distanceToPercent(distance);
   }
 
   /// Return the value of the _starts with dollar_ feature.
@@ -129,6 +157,9 @@
           ? -1.0
           : (proposedMemberName == containingMethodName ? 1.0 : 0.0);
 
+  /// Convert a [distance] to a percentage value and return the percentage.
+  double _distanceToPercent(int distance) => math.pow(0.95, distance);
+
   /// Return the inheritance distance between the [subclass] and the
   /// [superclass]. The set of [visited] elements is used to guard against
   /// cycles in the type graph.
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/probability_range.dart b/pkg/analysis_server/lib/src/services/completion/dart/probability_range.dart
index 0eefaa1..2b4be44 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/probability_range.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/probability_range.dart
@@ -18,6 +18,6 @@
   /// on the event represented by this range, return the probability of the
   /// event independent of the event based on this range.
   double conditionalProbability(double probability) {
-    return lower + (upper - lower) * probability;
+    return lower + ((upper - lower) * probability);
   }
 }
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/static_member_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/static_member_contributor.dart
index 9b0d656..a82924b 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/static_member_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/static_member_contributor.dart
@@ -112,9 +112,13 @@
     if (useNewRelevance) {
       var contextType = request.featureComputer
           .contextTypeFeature(request.contextType, elementType);
+      var elementKind = request.featureComputer
+          .elementKindFeature(element, request.opType.completionLocation);
       var hasDeprecated = request.featureComputer.hasDeprecatedFeature(element);
       relevance = _computeRelevance(
-          contextType: contextType, hasDeprecated: hasDeprecated);
+          contextType: contextType,
+          elementKind: elementKind,
+          hasDeprecated: hasDeprecated);
     }
     var suggestion = createSuggestion(request, element,
         completion: completion,
@@ -128,16 +132,15 @@
   /// Compute a relevance value from the given feature scores:
   /// - [contextType] is higher if the type of the element matches the context
   ///   type,
-  /// - [hasDeprecated] is higher if the element is not deprecated,
-  /// - [inheritanceDistance] is higher if the element is defined closer to the
-  ///   target type,
-  /// - [startsWithDollar] is higher if the element's name doe _not_ start with
-  ///   a dollar sign, and
-  /// - [superMatches] is higher if the element is being invoked through `super`
-  ///   and the element's name matches the name of the enclosing method.
+  /// - [elementKind] is higher if the kind of element occurs more frequently in
+  ///   the given location, and
+  /// - [hasDeprecated] is higher if the element is not deprecated.
   int _computeRelevance(
-      {@required double contextType, @required double hasDeprecated}) {
-    var score = weightedAverage([contextType, hasDeprecated], [1.0, 0.5]);
+      {@required double contextType,
+      @required double elementKind,
+      @required double hasDeprecated}) {
+    var score = weightedAverage(
+        [contextType, elementKind, hasDeprecated], [1.0, 0.75, 0.5]);
     return toRelevance(score, Relevance.member);
   }
 
diff --git a/pkg/analysis_server/lib/src/services/correction/assist.dart b/pkg/analysis_server/lib/src/services/correction/assist.dart
index 54a3100..50e59d7 100644
--- a/pkg/analysis_server/lib/src/services/correction/assist.dart
+++ b/pkg/analysis_server/lib/src/services/correction/assist.dart
@@ -28,15 +28,15 @@
 /// An enumeration of possible assist kinds.
 class DartAssistKind {
   static const ADD_DIAGNOSTIC_PROPERTY_REFERENCE = AssistKind(
-      'ADD_DIAGNOSTIC_PROPERTY_REFERENCE',
+      'dart.assist.add.diagnosticPropertyReference',
       30,
       'Add a debug reference to this property');
   static const ADD_NOT_NULL_ASSERT = AssistKind(
-      'dart.assist.addNotNullAssert', 30, 'Add a not-null assertion');
+      'dart.assist.add.notNullAssert', 30, 'Add a not-null assertion');
   static const ADD_RETURN_TYPE =
-      AssistKind('dart.assist.addReturnType', 30, 'Add return type');
+      AssistKind('dart.assist.add.returnType', 30, 'Add return type');
   static const ADD_TYPE_ANNOTATION =
-      AssistKind('dart.assist.addTypeAnnotation', 30, 'Add type annotation');
+      AssistKind('dart.assist.add.typeAnnotation', 30, 'Add type annotation');
   static const ASSIGN_TO_LOCAL_VARIABLE = AssistKind(
       'dart.assist.assignToVariable', 30, 'Assign value to new local variable');
   static const CONVERT_CLASS_TO_MIXIN = AssistKind(
@@ -82,9 +82,9 @@
       30,
       'Convert to field formal parameter');
   static const CONVERT_TO_FOR_ELEMENT = AssistKind(
-      'dart.assist.convertToForElement', 30, "Convert to a 'for' element");
+      'dart.assist.convert.toForElement', 30, "Convert to a 'for' element");
   static const CONVERT_TO_IF_ELEMENT = AssistKind(
-      'dart.assist.convertToIfElement', 30, "Convert to an 'if' element");
+      'dart.assist.convert.toIfElement', 30, "Convert to an 'if' element");
   static const CONVERT_TO_INT_LITERAL = AssistKind(
       'dart.assist.convert.toIntLiteral', 30, 'Convert to an int literal');
   static const CONVERT_TO_LIST_LITERAL = AssistKind(
@@ -115,7 +115,7 @@
       30,
       'Convert to single quoted string');
   static const CONVERT_TO_SPREAD =
-      AssistKind('dart.assist.convertToSpread', 30, 'Convert to a spread');
+      AssistKind('dart.assist.convert.toSpread', 30, 'Convert to a spread');
   static const ENCAPSULATE_FIELD =
       AssistKind('dart.assist.encapsulateField', 30, 'Encapsulate field');
   static const EXCHANGE_OPERANDS =
@@ -163,7 +163,7 @@
       AssistKind('dart.assist.flutter.removeWidget', 35, 'Remove this widget');
 
   static const IMPORT_ADD_SHOW = AssistKind(
-      'dart.assist.addShowCombinator', 30, "Add explicit 'show' combinator");
+      'dart.assist.add.showCombinator', 30, "Add explicit 'show' combinator");
   static const INLINE_INVOCATION =
       AssistKind('dart.assist.inline', 30, "Inline invocation of '{0}'");
   static const INTRODUCE_LOCAL_CAST_TYPE = AssistKind(
@@ -180,7 +180,7 @@
       'dart.assist.joinVariableDeclaration', 30, 'Join variable declaration');
   static const REMOVE_TYPE_ANNOTATION = AssistKind(
       // todo (pq): unify w/ fix
-      'dart.assist.removeTypeAnnotation',
+      'dart.assist.remove.typeAnnotation',
       29,
       'Remove type annotation');
   static const REPLACE_CONDITIONAL_WITH_IF_ELSE = AssistKind(
@@ -192,9 +192,7 @@
       30,
       "Replace 'if-else' with conditional ('c ? x : y')");
   static const REPLACE_WITH_VAR = AssistKind(
-      'dart.assist.convert.replaceWithVar',
-      30,
-      "Replace type annotation with 'var'");
+      'dart.assist.replace.withVar', 30, "Replace type annotation with 'var'");
   static const SHADOW_FIELD = AssistKind('dart.assist.shadowField', 30,
       'Create a local variable that shadows the field');
   static const SORT_CHILD_PROPERTY_LAST = AssistKind(
@@ -224,5 +222,5 @@
   static const SURROUND_WITH_WHILE =
       AssistKind('dart.assist.surround.while', 24, "Surround with 'while'");
   static const USE_CURLY_BRACES =
-      AssistKind('USE_CURLY_BRACES', 30, 'Use curly braces');
+      AssistKind('dart.assist.surround.curlyBraces', 30, 'Use curly braces');
 }
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 8a8e8ec..51a755e 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -85,6 +85,9 @@
             errorCode.name == LintNames.null_closures ||
             errorCode.name == LintNames.prefer_collection_literals ||
             errorCode.name == LintNames.prefer_conditional_assignment ||
+            errorCode.name == LintNames.prefer_const_constructors ||
+            errorCode.name ==
+                LintNames.prefer_const_constructors_in_immutables ||
             errorCode.name == LintNames.prefer_const_declarations ||
             errorCode.name == LintNames.prefer_equal_for_default_values ||
             errorCode.name == LintNames.prefer_final_fields ||
@@ -103,7 +106,8 @@
 /// An enumeration of quick fix kinds for the errors found in an analysis
 /// options file.
 class AnalysisOptionsFixKind {
-  static const REMOVE_SETTING = FixKind('REMOVE_SETTING', 50, "Remove '{0}'");
+  static const REMOVE_SETTING =
+      FixKind('analysisOptions.fix.removeSetting', 50, "Remove '{0}'");
 }
 
 /// The implementation of [DartFixContext].
@@ -131,295 +135,332 @@
 
 /// An enumeration of quick fix kinds found in a Dart file.
 class DartFixKind {
-  static const ADD_ASYNC = FixKind('ADD_ASYNC', 50, "Add 'async' modifier");
-  static const ADD_AWAIT = FixKind('ADD_AWAIT', 50, "Add 'await' keyword");
-  static const ADD_EXPLICIT_CAST = FixKind('ADD_EXPLICIT_CAST', 50, 'Add cast',
+  static const ADD_ASYNC =
+      FixKind('dart.fix.add.async', 50, "Add 'async' modifier");
+  static const ADD_AWAIT =
+      FixKind('dart.fix.add.await', 50, "Add 'await' keyword");
+  static const ADD_EXPLICIT_CAST = FixKind(
+      'dart.fix.add.explicitCast', 50, 'Add cast',
       appliedTogetherMessage: 'Add all casts in file');
-  static const ADD_CONST = FixKind('ADD_CONST', 50, "Add 'const' modifier");
+  static const ADD_CONST =
+      FixKind('dart.fix.add.const', 50, "Add 'const' modifier");
   static const ADD_CURLY_BRACES =
-      FixKind('ADD_CURLY_BRACES', 50, 'Add curly braces');
+      FixKind('dart.fix.add.curlyBraces', 50, 'Add curly braces');
   static const ADD_DIAGNOSTIC_PROPERTY_REFERENCE = FixKind(
-      'ADD_DIAGNOSTIC_PROPERTY_REFERENCE',
+      'dart.fix.add.diagnosticPropertyReference',
       50,
       'Add a debug reference to this property');
   static const ADD_FIELD_FORMAL_PARAMETERS = FixKind(
-      'ADD_FIELD_FORMAL_PARAMETERS', 70, 'Add final field formal parameters');
-  static const ADD_MISSING_ENUM_CASE_CLAUSES =
-      FixKind('ADD_MISSING_ENUM_CASE_CLAUSES', 50, 'Add missing case clauses');
-  static const ADD_MISSING_PARAMETER_NAMED =
-      FixKind('ADD_MISSING_PARAMETER_NAMED', 70, "Add named parameter '{0}'");
+      'dart.fix.add.fieldFormalParameters',
+      70,
+      'Add final field formal parameters');
+  static const ADD_MISSING_ENUM_CASE_CLAUSES = FixKind(
+      'dart.fix.add.missingEnumCaseClauses', 50, 'Add missing case clauses');
+  static const ADD_MISSING_PARAMETER_NAMED = FixKind(
+      'dart.fix.add.missingParameterNamed', 70, "Add named parameter '{0}'");
   static const ADD_MISSING_PARAMETER_POSITIONAL = FixKind(
-      'ADD_MISSING_PARAMETER_POSITIONAL',
+      'dart.fix.add.missingParameterPositional',
       69,
       'Add optional positional parameter');
-  static const ADD_MISSING_PARAMETER_REQUIRED =
-      FixKind('ADD_MISSING_PARAMETER_REQUIRED', 70, 'Add required parameter');
+  static const ADD_MISSING_PARAMETER_REQUIRED = FixKind(
+      'dart.fix.add.missingParameterRequired', 70, 'Add required parameter');
   static const ADD_MISSING_REQUIRED_ARGUMENT = FixKind(
-      'ADD_MISSING_REQUIRED_ARGUMENT', 70, "Add required argument '{0}'");
-  static const ADD_NE_NULL = FixKind('ADD_NE_NULL', 50, 'Add != null',
+      'dart.fix.add.missingRequiredArgument',
+      70,
+      "Add required argument '{0}'");
+  static const ADD_NE_NULL = FixKind('dart.fix.add.neNull', 50, 'Add != null',
       appliedTogetherMessage: 'Add != null everywhere in file');
   static const ADD_OVERRIDE =
-      FixKind('ADD_OVERRIDE', 50, "Add '@override' annotation");
+      FixKind('dart.fix.add.override', 50, "Add '@override' annotation");
   static const ADD_REQUIRED =
-      FixKind('ADD_REQUIRED', 50, "Add '@required' annotation");
+      FixKind('dart.fix.add.required', 50, "Add '@required' annotation");
   static const ADD_RETURN_TYPE =
-      FixKind('ADD_RETURN_TYPE', 50, 'Add return type');
-  static const ADD_STATIC = FixKind('ADD_STATIC', 50, "Add 'static' modifier");
+      FixKind('dart.fix.add.returnType', 50, 'Add return type');
+  static const ADD_STATIC =
+      FixKind('dart.fix.add.static', 50, "Add 'static' modifier");
   static const ADD_SUPER_CONSTRUCTOR_INVOCATION = FixKind(
-      'ADD_SUPER_CONSTRUCTOR_INVOCATION',
+      'dart.fix.add.superConstructorInvocation',
       50,
       'Add super constructor {0} invocation');
   static const ADD_TYPE_ANNOTATION =
-      FixKind('ADD_TYPE_ANNOTATION', 50, 'Add type annotation');
+      FixKind('dart.fix.add.typeAnnotation', 50, 'Add type annotation');
   static const CHANGE_ARGUMENT_NAME =
-      FixKind('CHANGE_ARGUMENT_NAME', 60, "Change to '{0}'");
-  static const CHANGE_TO = FixKind('CHANGE_TO', 51, "Change to '{0}'");
+      FixKind('dart.fix.change.argumentName', 60, "Change to '{0}'");
+  static const CHANGE_TO = FixKind('dart.fix.change.to', 51, "Change to '{0}'");
   static const CHANGE_TO_NEAREST_PRECISE_VALUE = FixKind(
-      'CHANGE_TO_NEAREST_PRECISE_VALUE',
+      'dart.fix.change.toNearestPreciseValue',
       50,
       'Change to nearest precise int-as-double value: {0}');
   static const CHANGE_TO_STATIC_ACCESS = FixKind(
-      'CHANGE_TO_STATIC_ACCESS', 50, "Change access to static using '{0}'");
+      'dart.fix.change.toStaticAccess',
+      50,
+      "Change access to static using '{0}'");
   static const CHANGE_TYPE_ANNOTATION = FixKind(
-      'CHANGE_TYPE_ANNOTATION', 50, "Change '{0}' to '{1}' type annotation");
-  static const CONVERT_FLUTTER_CHILD =
-      FixKind('CONVERT_FLUTTER_CHILD', 50, 'Convert to children:');
-  static const CONVERT_FLUTTER_CHILDREN =
-      FixKind('CONVERT_FLUTTER_CHILDREN', 50, 'Convert to child:');
-  static const CONVERT_INTO_EXPRESSION_BODY =
-      FixKind('CONVERT_INTO_EXPRESSION_BODY', 50, 'Convert to expression body');
+      'dart.fix.change.typeAnnotation',
+      50,
+      "Change '{0}' to '{1}' type annotation");
+  static const CONVERT_FLUTTER_CHILD = FixKind(
+      'dart.fix.flutter.convert.childToChildren', 50, 'Convert to children:');
+  static const CONVERT_FLUTTER_CHILDREN = FixKind(
+      'dart.fix.flutter.convert.childrenToChild', 50, 'Convert to child:');
+  static const CONVERT_INTO_EXPRESSION_BODY = FixKind(
+      'dart.fix.convert.toExpressionBody', 50, 'Convert to expression body');
   static const CONVERT_TO_CONTAINS =
-      FixKind('CONVERT_TO_CONTAINS', 50, "Convert to using 'contains'");
-  static const CONVERT_TO_FOR_ELEMENT =
-      FixKind('CONVERT_TO_FOR_ELEMENT', 50, "Convert to a 'for' element");
+      FixKind('dart.fix.convert.toContains', 50, "Convert to using 'contains'");
+  static const CONVERT_TO_FOR_ELEMENT = FixKind(
+      'dart.fix.convert.toForElement', 50, "Convert to a 'for' element");
   static const CONVERT_TO_GENERIC_FUNCTION_SYNTAX = FixKind(
-      'CONVERT_TO_GENERIC_FUNCTION_SYNTAX',
+      'dart.fix.convert.toGenericFunctionSyntax',
       50,
       "Convert into 'Function' syntax");
   static const CONVERT_TO_IF_ELEMENT =
-      FixKind('CONVERT_TO_IF_ELEMENT', 50, "Convert to an 'if' element");
+      FixKind('dart.fix.convert.toIfElement', 50, "Convert to an 'if' element");
   static const CONVERT_TO_IF_NULL =
-      FixKind('CONVERT_TO_IF_NULL', 50, "Convert to use '??'");
+      FixKind('dart.fix.convert.toIfNull', 50, "Convert to use '??'");
   static const CONVERT_TO_INT_LITERAL =
-      FixKind('CONVERT_TO_INT_LITERAL', 50, 'Convert to an int literal');
+      FixKind('dart.fix.convert.toIntLiteral', 50, 'Convert to an int literal');
   static const CONVERT_TO_LINE_COMMENT = FixKind(
-      'CONVERT_TO_LINE_COMMENT', 50, 'Convert to line documentation comment');
+      'dart.fix.convert.toLineComment',
+      50,
+      'Convert to line documentation comment');
   static const CONVERT_TO_LIST_LITERAL =
-      FixKind('CONVERT_TO_LIST_LITERAL', 50, 'Convert to list literal');
+      FixKind('dart.fix.convert.toListLiteral', 50, 'Convert to list literal');
   static const CONVERT_TO_MAP_LITERAL =
-      FixKind('CONVERT_TO_MAP_LITERAL', 50, 'Convert to map literal');
-  static const CONVERT_TO_NAMED_ARGUMENTS =
-      FixKind('CONVERT_TO_NAMED_ARGUMENTS', 50, 'Convert to named arguments');
+      FixKind('dart.fix.convert.toMapLiteral', 50, 'Convert to map literal');
+  static const CONVERT_TO_NAMED_ARGUMENTS = FixKind(
+      'dart.fix.convert.toNamedArguments', 50, 'Convert to named arguments');
   static const CONVERT_TO_NULL_AWARE =
-      FixKind('CONVERT_TO_NULL_AWARE', 50, "Convert to use '?.'");
-  static const CONVERT_TO_PACKAGE_IMPORT =
-      FixKind('CONVERT_TO_PACKAGE_IMPORT', 50, "Convert to 'package:' import");
-  static const CONVERT_TO_RELATIVE_IMPORT =
-      FixKind('CONVERT_TO_RELATIVE_IMPORT', 50, 'Convert to relative import');
+      FixKind('dart.fix.convert.toNullAware', 50, "Convert to use '?.'");
+  static const CONVERT_TO_PACKAGE_IMPORT = FixKind(
+      'dart.fix.convert.toPackageImport', 50, "Convert to 'package:' import");
+  static const CONVERT_TO_RELATIVE_IMPORT = FixKind(
+      'dart.fix.convert.toRelativeImport', 50, 'Convert to relative import');
   static const CONVERT_TO_SET_LITERAL =
-      FixKind('CONVERT_TO_SET_LITERAL', 50, 'Convert to set literal');
+      FixKind('dart.fix.convert.toSetLiteral', 50, 'Convert to set literal');
   static const CONVERT_TO_SINGLE_QUOTED_STRING = FixKind(
-      'CONVERT_TO_SINGLE_QUOTED_STRING', 50, 'Convert to single quoted string');
+      'dart.fix.convert.toSingleQuotedString',
+      50,
+      'Convert to single quoted string');
   static const CONVERT_TO_SPREAD =
-      FixKind('CONVERT_TO_SPREAD', 50, 'Convert to a spread');
-  static const CONVERT_TO_WHERE_TYPE =
-      FixKind('CONVERT_TO_WHERE_TYPE', 50, "Convert to a use 'whereType'");
-  static const CREATE_CLASS = FixKind('CREATE_CLASS', 50, "Create class '{0}'");
+      FixKind('dart.fix.convert.toSpread', 50, 'Convert to a spread');
+  static const CONVERT_TO_WHERE_TYPE = FixKind(
+      'dart.fix.convert.toWhereType', 50, "Convert to a use 'whereType'");
+  static const CREATE_CLASS =
+      FixKind('dart.fix.create.class', 50, "Create class '{0}'");
   static const CREATE_CONSTRUCTOR =
-      FixKind('CREATE_CONSTRUCTOR', 50, "Create constructor '{0}'");
+      FixKind('dart.fix.create.constructor', 50, "Create constructor '{0}'");
   static const CREATE_CONSTRUCTOR_FOR_FINAL_FIELDS = FixKind(
-      'CREATE_CONSTRUCTOR_FOR_FINAL_FIELDS',
+      'dart.fix.create.constructorForFinalFields',
       50,
       'Create constructor for final fields');
-  static const CREATE_CONSTRUCTOR_SUPER =
-      FixKind('CREATE_CONSTRUCTOR_SUPER', 50, 'Create constructor to call {0}');
-  static const CREATE_FIELD = FixKind('CREATE_FIELD', 49, "Create field '{0}'");
-  static const CREATE_FILE = FixKind('CREATE_FILE', 50, "Create file '{0}'");
+  static const CREATE_CONSTRUCTOR_SUPER = FixKind(
+      'dart.fix.create.constructorSuper', 50, 'Create constructor to call {0}');
+  static const CREATE_FIELD =
+      FixKind('dart.fix.create.field', 49, "Create field '{0}'");
+  static const CREATE_FILE =
+      FixKind('dart.fix.create.file', 50, "Create file '{0}'");
   static const CREATE_FUNCTION =
-      FixKind('CREATE_FUNCTION', 49, "Create function '{0}'");
+      FixKind('dart.fix.create.function', 49, "Create function '{0}'");
   static const CREATE_GETTER =
-      FixKind('CREATE_GETTER', 50, "Create getter '{0}'");
-  static const CREATE_LOCAL_VARIABLE =
-      FixKind('CREATE_LOCAL_VARIABLE', 50, "Create local variable '{0}'");
+      FixKind('dart.fix.create.getter', 50, "Create getter '{0}'");
+  static const CREATE_LOCAL_VARIABLE = FixKind(
+      'dart.fix.create.localVariable', 50, "Create local variable '{0}'");
   static const CREATE_METHOD =
-      FixKind('CREATE_METHOD', 50, "Create method '{0}'");
-  static const CREATE_MISSING_OVERRIDES =
-      FixKind('CREATE_MISSING_OVERRIDES', 51, 'Create {0} missing override(s)');
-  static const CREATE_MIXIN = FixKind('CREATE_MIXIN', 50, "Create mixin '{0}'");
-  static const CREATE_NO_SUCH_METHOD =
-      FixKind('CREATE_NO_SUCH_METHOD', 49, "Create 'noSuchMethod' method");
+      FixKind('dart.fix.create.method', 50, "Create method '{0}'");
+  static const CREATE_MISSING_OVERRIDES = FixKind(
+      'dart.fix.create.missingOverrides', 51, 'Create {0} missing override(s)');
+  static const CREATE_MIXIN =
+      FixKind('dart.fix.create.mixin', 50, "Create mixin '{0}'");
+  static const CREATE_NO_SUCH_METHOD = FixKind(
+      'dart.fix.create.noSuchMethod', 49, "Create 'noSuchMethod' method");
   static const CREATE_SETTER =
-      FixKind('CREATE_SETTER', 50, "Create setter '{0}'");
+      FixKind('dart.fix.create.setter', 50, "Create setter '{0}'");
   static const EXTEND_CLASS_FOR_MIXIN =
-      FixKind('EXTEND_CLASS_FOR_MIXIN', 50, "Extend the class '{0}'");
+      FixKind('dart.fix.extendClassForMixin', 50, "Extend the class '{0}'");
   static const IMPORT_ASYNC =
-      FixKind('IMPORT_ASYNC', 49, "Import 'dart:async'");
-  static const IMPORT_LIBRARY_PREFIX = FixKind('IMPORT_LIBRARY_PREFIX', 49,
-      "Use imported library '{0}' with prefix '{1}'");
+      FixKind('dart.fix.import.async', 49, "Import 'dart:async'");
+  static const IMPORT_LIBRARY_PREFIX = FixKind('dart.fix.import.libraryPrefix',
+      49, "Use imported library '{0}' with prefix '{1}'");
   static const IMPORT_LIBRARY_PROJECT1 =
-      FixKind('IMPORT_LIBRARY_PROJECT1', 53, "Import library '{0}'");
+      FixKind('dart.fix.import.libraryProject1', 53, "Import library '{0}'");
   static const IMPORT_LIBRARY_PROJECT2 =
-      FixKind('IMPORT_LIBRARY_PROJECT2', 52, "Import library '{0}'");
+      FixKind('dart.fix.import.libraryProject2', 52, "Import library '{0}'");
   static const IMPORT_LIBRARY_PROJECT3 =
-      FixKind('IMPORT_LIBRARY_PROJECT3', 51, "Import library '{0}'");
+      FixKind('dart.fix.import.libraryProject3', 51, "Import library '{0}'");
   static const IMPORT_LIBRARY_SDK =
-      FixKind('IMPORT_LIBRARY_SDK', 54, "Import library '{0}'");
+      FixKind('dart.fix.import.librarySdk', 54, "Import library '{0}'");
   static const IMPORT_LIBRARY_SHOW =
-      FixKind('IMPORT_LIBRARY_SHOW', 55, "Update library '{0}' import");
+      FixKind('dart.fix.import.libraryShow', 55, "Update library '{0}' import");
   static const INLINE_INVOCATION =
-      FixKind('INLINE_INVOCATION', 30, "Inline invocation of '{0}'");
+      FixKind('dart.fix.inlineInvocation', 30, "Inline invocation of '{0}'");
   static const INLINE_TYPEDEF =
-      FixKind('INLINE_TYPEDEF', 30, "Inline the definition of '{0}'");
-  static const INSERT_SEMICOLON = FixKind('INSERT_SEMICOLON', 50, "Insert ';'");
+      FixKind('dart.fix.inlineTypedef', 30, "Inline the definition of '{0}'");
+  static const INSERT_SEMICOLON =
+      FixKind('dart.fix.insertSemicolon', 50, "Insert ';'");
   static const MAKE_CLASS_ABSTRACT =
-      FixKind('MAKE_CLASS_ABSTRACT', 50, "Make class '{0}' abstract");
+      FixKind('dart.fix.makeClassAbstract', 50, "Make class '{0}' abstract");
   static const MAKE_FIELD_NOT_FINAL =
-      FixKind('MAKE_FIELD_NOT_FINAL', 50, "Make field '{0}' not final");
-  static const MAKE_FINAL = FixKind('MAKE_FINAL', 50, 'Make final');
+      FixKind('dart.fix.makeFieldNotFinal', 50, "Make field '{0}' not final");
+  static const MAKE_FINAL = FixKind('dart.fix.makeFinal', 50, 'Make final');
   static const MOVE_TYPE_ARGUMENTS_TO_CLASS = FixKind(
-      'MOVE_TYPE_ARGUMENTS_TO_CLASS',
+      'dart.fix.moveTypeArgumentsToClass',
       50,
       'Move type arguments to after class name');
-  static const MAKE_VARIABLE_NOT_FINAL =
-      FixKind('MAKE_VARIABLE_NOT_FINAL', 50, "Make variable '{0}' not final");
+  static const MAKE_VARIABLE_NOT_FINAL = FixKind(
+      'dart.fix.makeVariableNotFinal', 50, "Make variable '{0}' not final");
   static const QUALIFY_REFERENCE =
-      FixKind('QUALIFY_REFERENCE', 50, "Use '{0}'");
+      FixKind('dart.fix.qualifyReference', 50, "Use '{0}'");
   static const REMOVE_ANNOTATION =
-      FixKind('REMOVE_ANNOTATION', 50, "Remove the '{0}' annotation");
+      FixKind('dart.fix.remove.annotation', 50, "Remove the '{0}' annotation");
   static const REMOVE_ARGUMENT =
-      FixKind('REMOVE_ARGUMENT', 50, 'Remove argument');
-  static const REMOVE_AWAIT = FixKind('REMOVE_AWAIT', 50, 'Remove await');
-  static const REMOVE_CONST = FixKind('REMOVE_CONST', 50, 'Remove const');
+      FixKind('dart.fix.remove.argument', 50, 'Remove argument');
+  static const REMOVE_AWAIT =
+      FixKind('dart.fix.remove.await', 50, 'Remove await');
+  static const REMOVE_CONST =
+      FixKind('dart.fix.remove.const', 50, 'Remove const');
   static const REMOVE_DEAD_CODE =
-      FixKind('REMOVE_DEAD_CODE', 50, 'Remove dead code');
-  static const REMOVE_DUPLICATE_CASE =
-      FixKind('REMOVE_DUPLICATE_CASE', 50, 'Remove duplicate case statement');
+      FixKind('dart.fix.remove.deadCode', 50, 'Remove dead code');
+  static const REMOVE_DUPLICATE_CASE = FixKind(
+      'dart.fix.remove.duplicateCase', 50, 'Remove duplicate case statement');
   static const REMOVE_EMPTY_CATCH =
-      FixKind('REMOVE_EMPTY_CATCH', 50, 'Remove empty catch clause');
+      FixKind('dart.fix.remove.emptyCatch', 50, 'Remove empty catch clause');
   static const REMOVE_EMPTY_CONSTRUCTOR_BODY = FixKind(
-      'REMOVE_EMPTY_CONSTRUCTOR_BODY', 50, 'Remove empty constructor body');
+      'dart.fix.remove.emptyConstructorBody',
+      50,
+      'Remove empty constructor body');
   static const REMOVE_EMPTY_ELSE =
-      FixKind('REMOVE_EMPTY_ELSE', 50, 'Remove empty else clause');
+      FixKind('dart.fix.remove.emptyElse', 50, 'Remove empty else clause');
   static const REMOVE_EMPTY_STATEMENT =
-      FixKind('REMOVE_EMPTY_STATEMENT', 50, 'Remove empty statement');
+      FixKind('dart.fix.remove.emptyStatement', 50, 'Remove empty statement');
   static const REMOVE_IF_NULL_OPERATOR =
-      FixKind('REMOVE_IF_NULL_OPERATOR', 50, "Remove the '??' operator");
+      FixKind('dart.fix.remove.ifNullOperator', 50, "Remove the '??' operator");
   static const REMOVE_INITIALIZER =
-      FixKind('REMOVE_INITIALIZER', 50, 'Remove initializer');
+      FixKind('dart.fix.remove.initializer', 50, 'Remove initializer');
   static const REMOVE_INTERPOLATION_BRACES = FixKind(
-      'REMOVE_INTERPOLATION_BRACES',
+      'dart.fix.remove.interpolationBraces',
       50,
       'Remove unnecessary interpolation braces');
-  static const REMOVE_METHOD_DECLARATION =
-      FixKind('REMOVE_METHOD_DECLARATION', 50, 'Remove method declaration');
-  static const REMOVE_NAME_FROM_COMBINATOR =
-      FixKind('REMOVE_NAME_FROM_COMBINATOR', 50, "Remove name from '{0}'");
+  static const REMOVE_METHOD_DECLARATION = FixKind(
+      'dart.fix.remove.methodDeclaration', 50, 'Remove method declaration');
+  static const REMOVE_NAME_FROM_COMBINATOR = FixKind(
+      'dart.fix.remove.nameFromCombinator', 50, "Remove name from '{0}'");
   static const REMOVE_OPERATOR =
-      FixKind('REMOVE_OPERATOR', 50, 'Remove the operator');
+      FixKind('dart.fix.remove.operator', 50, 'Remove the operator');
   static const REMOVE_PARAMETERS_IN_GETTER_DECLARATION = FixKind(
-      'REMOVE_PARAMETERS_IN_GETTER_DECLARATION',
+      'dart.fix.remove.parametersInGetterDeclaration',
       50,
       'Remove parameters in getter declaration');
   static const REMOVE_PARENTHESIS_IN_GETTER_INVOCATION = FixKind(
-      'REMOVE_PARENTHESIS_IN_GETTER_INVOCATION',
+      'dart.fix.remove.parenthesisInGetterInvocation',
       50,
       'Remove parentheses in getter invocation');
   static const REMOVE_THIS_EXPRESSION =
-      FixKind('REMOVE_THIS_EXPRESSION', 50, 'Remove this expression');
+      FixKind('dart.fix.remove.thisExpression', 50, 'Remove this expression');
   static const REMOVE_TYPE_ANNOTATION =
-      FixKind('REMOVE_TYPE_ANNOTATION', 50, 'Remove type annotation');
+      FixKind('dart.fix.remove.typeAnnotation', 50, 'Remove type annotation');
   static const REMOVE_TYPE_ARGUMENTS =
-      FixKind('REMOVE_TYPE_ARGUMENTS', 49, 'Remove type arguments');
+      FixKind('dart.fix.remove.typeArguments', 49, 'Remove type arguments');
   static const REMOVE_UNNECESSARY_CAST = FixKind(
-      'REMOVE_UNNECESSARY_CAST', 50, 'Remove unnecessary cast',
+      'dart.fix.remove.unnecessaryCast', 50, 'Remove unnecessary cast',
       appliedTogetherMessage: 'Remove all unnecessary casts in file');
   static const REMOVE_UNNECESSARY_CONST = FixKind(
-      'REMOVE_UNNECESSARY_CONST', 50, 'Remove unnecessary const keyword');
-  static const REMOVE_UNNECESSARY_NEW =
-      FixKind('REMOVE_UNNECESSARY_NEW', 50, 'Remove unnecessary new keyword');
-  static const REMOVE_UNUSED_CATCH_CLAUSE =
-      FixKind('REMOVE_UNUSED_CATCH_CLAUSE', 50, "Remove unused 'catch' clause");
+      'dart.fix.remove.unnecessaryConst',
+      50,
+      'Remove unnecessary const keyword');
+  static const REMOVE_UNNECESSARY_NEW = FixKind(
+      'dart.fix.remove.unnecessaryNew', 50, 'Remove unnecessary new keyword');
+  static const REMOVE_UNUSED_CATCH_CLAUSE = FixKind(
+      'dart.fix.remove.unusedCatchClause', 50, "Remove unused 'catch' clause");
   static const REMOVE_UNUSED_CATCH_STACK = FixKind(
-      'REMOVE_UNUSED_CATCH_STACK', 50, 'Remove unused stack trace variable');
+      'dart.fix.remove.unusedCatchStack',
+      50,
+      'Remove unused stack trace variable');
   static const REMOVE_UNUSED_ELEMENT =
-      FixKind('REMOVE_UNUSED_ELEMENT', 50, 'Remove unused element');
+      FixKind('dart.fix.remove.unusedElement', 50, 'Remove unused element');
   static const REMOVE_UNUSED_FIELD =
-      FixKind('REMOVE_UNUSED_FIELD', 50, 'Remove unused field');
+      FixKind('dart.fix.remove.unusedField', 50, 'Remove unused field');
   static const REMOVE_UNUSED_IMPORT = FixKind(
-      'REMOVE_UNUSED_IMPORT', 50, 'Remove unused import',
+      'dart.fix.remove.unusedImport', 50, 'Remove unused import',
       appliedTogetherMessage: 'Remove all unused imports in this file');
   static const REMOVE_UNUSED_LABEL =
-      FixKind('REMOVE_UNUSED_LABEL', 50, 'Remove unused label');
+      FixKind('dart.fix.remove.unusedLabel', 50, 'Remove unused label');
   static const REMOVE_UNUSED_LOCAL_VARIABLE = FixKind(
-      'REMOVE_UNUSED_LOCAL_VARIABLE', 50, 'Remove unused local variable');
+      'dart.fix.remove.unusedLocalVariable',
+      50,
+      'Remove unused local variable');
   static const RENAME_TO_CAMEL_CASE =
-      FixKind('RENAME_TO_CAMEL_CASE', 50, "Rename to '{0}'");
+      FixKind('dart.fix.rename.toCamelCase', 50, "Rename to '{0}'");
   static const REPLACE_BOOLEAN_WITH_BOOL = FixKind(
-      'REPLACE_BOOLEAN_WITH_BOOL', 50, "Replace 'boolean' with 'bool'",
+      'dart.fix.replace.booleanWithBool', 50, "Replace 'boolean' with 'bool'",
       appliedTogetherMessage: "Replace all 'boolean' with 'bool' in file");
   static const REPLACE_COLON_WITH_EQUALS =
-      FixKind('REPLACE_COLON_WITH_EQUALS', 50, "Replace ':' with '='");
-  static const REPLACE_FINAL_WITH_CONST =
-      FixKind('REPLACE_FINAL_WITH_CONST', 50, "Replace 'final' with 'const'");
-  static const REPLACE_NEW_WITH_CONST =
-      FixKind('REPLACE_NEW_WITH_CONST', 50, "Replace 'new' with 'const'");
-  static const REPLACE_NULL_WITH_CLOSURE =
-      FixKind('REPLACE_NULL_WITH_CLOSURE', 50, "Replace 'null' with a closure");
+      FixKind('dart.fix.replace.colonWithEquals', 50, "Replace ':' with '='");
+  static const REPLACE_FINAL_WITH_CONST = FixKind(
+      'dart.fix.replace.finalWithConst', 50, "Replace 'final' with 'const'");
+  static const REPLACE_NEW_WITH_CONST = FixKind(
+      'dart.fix.replace.newWithConst', 50, "Replace 'new' with 'const'");
+  static const REPLACE_NULL_WITH_CLOSURE = FixKind(
+      'dart.fix.replace.nullWithClosure', 50, "Replace 'null' with a closure");
   static const REPLACE_RETURN_TYPE_FUTURE = FixKind(
-      'REPLACE_RETURN_TYPE_FUTURE',
+      'dart.fix.replace.returnTypeFuture',
       50,
       "Return 'Future' from 'async' function");
-  static const REPLACE_VAR_WITH_DYNAMIC =
-      FixKind('REPLACE_VAR_WITH_DYNAMIC', 50, "Replace 'var' with 'dynamic'");
+  static const REPLACE_VAR_WITH_DYNAMIC = FixKind(
+      'dart.fix.replace.varWithDynamic', 50, "Replace 'var' with 'dynamic'");
   static const REPLACE_WITH_EIGHT_DIGIT_HEX =
-      FixKind('REPLACE_WITH_EIGHT_DIGIT_HEX', 50, "Replace with '{0}'");
+      FixKind('dart.fix.replace.withEightDigitHex', 50, "Replace with '{0}'");
   static const REPLACE_WITH_BRACKETS =
-      FixKind('REPLACE_WITH_BRACKETS', 50, 'Replace with { }');
-  static const REPLACE_WITH_CONDITIONAL_ASSIGNMENT =
-      FixKind('REPLACE_WITH_CONDITIONAL_ASSIGNMENT', 50, 'Replace with ??=');
+      FixKind('dart.fix.replace.withBrackets', 50, 'Replace with { }');
+  static const REPLACE_WITH_CONDITIONAL_ASSIGNMENT = FixKind(
+      'dart.fix.replace.withConditionalAssignment', 50, 'Replace with ??=');
   static const REPLACE_WITH_EXTENSION_NAME =
-      FixKind('REPLACE_WITH_EXTENSION_NAME', 50, "Replace with '{0}'");
+      FixKind('dart.fix.replace.withExtensionName', 50, "Replace with '{0}'");
   static const REPLACE_WITH_IDENTIFIER =
-      FixKind('REPLACE_WITH_IDENTIFIER', 50, 'Replace with identifier');
-  static const REPLACE_WITH_INTERPOLATION =
-      FixKind('REPLACE_WITH_INTERPOLATION', 50, 'Replace with interpolation');
+      FixKind('dart.fix.replace.withIdentifier', 50, 'Replace with identifier');
+  static const REPLACE_WITH_INTERPOLATION = FixKind(
+      'dart.fix.replace.withInterpolation', 50, 'Replace with interpolation');
   static const REPLACE_WITH_IS_EMPTY =
-      FixKind('REPLACE_WITH_IS_EMPTY', 50, "Replace with 'isEmpty'");
-  static const REPLACE_WITH_IS_NOT_EMPTY =
-      FixKind('REPLACE_WITH_IS_NOT_EMPTY', 50, "Replace with 'isNotEmpty'");
-  static const REPLACE_WITH_NULL_AWARE = FixKind('REPLACE_WITH_NULL_AWARE', 50,
+      FixKind('dart.fix.replace.withIsEmpty', 50, "Replace with 'isEmpty'");
+  static const REPLACE_WITH_IS_NOT_EMPTY = FixKind(
+      'dart.fix.replace.withIsNotEmpty', 50, "Replace with 'isNotEmpty'");
+  static const REPLACE_WITH_NULL_AWARE = FixKind(
+      'dart.fix.replace.withNullAware',
+      50,
       "Replace the '.' with a '?.' in the invocation");
-  static const REPLACE_WITH_TEAR_OFF = FixKind(
-      'REPLACE_WITH_TEAR_OFF', 50, 'Replace function literal with tear-off');
-  static const REPLACE_WITH_VAR =
-      FixKind('REPLACE_WITH_VAR', 50, "Replace type annotation with 'var'");
-  static const SORT_CHILD_PROPERTY_LAST = FixKind('SORT_CHILD_PROPERTY_LAST',
-      50, 'Move child property to end of arguments');
+  static const REPLACE_WITH_TEAR_OFF = FixKind('dart.fix.replace.withTearOff',
+      50, 'Replace function literal with tear-off');
+  static const REPLACE_WITH_VAR = FixKind(
+      'dart.fix.replace.withVar', 50, "Replace type annotation with 'var'");
+  static const SORT_CHILD_PROPERTY_LAST = FixKind(
+      'dart.fix.sort.childPropertyLast',
+      50,
+      'Move child property to end of arguments');
   static const SORT_DIRECTIVES =
-      FixKind('SORT_DIRECTIVES', 50, 'Sort directives');
-  static const UPDATE_SDK_CONSTRAINTS =
-      FixKind('UPDATE_SDK_CONSTRAINTS', 50, 'Update the SDK constraints');
-  static const USE_CONST = FixKind('USE_CONST', 50, 'Change to constant');
+      FixKind('dart.fix.sort.directives', 50, 'Sort directives');
+  static const UPDATE_SDK_CONSTRAINTS = FixKind(
+      'dart.fix.updateSdkConstraints', 50, 'Update the SDK constraints');
+  static const USE_CONST =
+      FixKind('dart.fix.use.const', 50, 'Change to constant');
   static const USE_EFFECTIVE_INTEGER_DIVISION = FixKind(
-      'USE_EFFECTIVE_INTEGER_DIVISION',
+      'dart.fix.use.effectiveIntegerDivision',
       50,
       'Use effective integer division ~/');
   static const USE_EQ_EQ_NULL = FixKind(
-      'USE_EQ_EQ_NULL', 50, "Use == null instead of 'is Null'",
+      'dart.fix.use.eqEqNull', 50, "Use == null instead of 'is Null'",
       appliedTogetherMessage:
           "Use == null instead of 'is Null' everywhere in file");
-  static const USE_IS_NOT_EMPTY = FixKind(
-      'USE_IS_NOT_EMPTY', 50, "Use x.isNotEmpty instead of '!x.isEmpty'");
+  static const USE_IS_NOT_EMPTY = FixKind('dart.fix.use.isNotEmpty', 50,
+      "Use x.isNotEmpty instead of '!x.isEmpty'");
   static const USE_NOT_EQ_NULL = FixKind(
-      'USE_NOT_EQ_NULL', 50, "Use != null instead of 'is! Null'",
+      'dart.fix.use.notEqNull', 50, "Use != null instead of 'is! Null'",
       appliedTogetherMessage:
           "Use != null instead of 'is! Null' everywhere in file");
   static const USE_RETHROW =
-      FixKind('USE_RETHROW', 50, 'Replace throw with rethrow');
+      FixKind('dart.fix.use.rethrow', 50, 'Replace throw with rethrow');
   static const WRAP_IN_FUTURE =
-      FixKind('WRAP_IN_FUTURE', 50, "Wrap in 'Future.value'");
+      FixKind('dart.fix.wrap.future', 50, "Wrap in 'Future.value'");
   static const WRAP_IN_TEXT =
-      FixKind('WRAP_IN_TEXT', 50, "Wrap in a 'Text' widget");
+      FixKind('dart.fix.flutter.wrap.text', 50, "Wrap in a 'Text' widget");
 }
 
 /// An enumeration of quick fix kinds for the errors found in an Android
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index 98df9e6..1965a45 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -4470,6 +4470,7 @@
     if (change.edits.isEmpty && !importsOnly) {
       return;
     }
+    change.id = kind.id;
     change.message = formatList(kind.message, args);
     fixes.add(Fix(kind, change));
   }
diff --git a/pkg/analysis_server/pubspec.yaml b/pkg/analysis_server/pubspec.yaml
index 2ea56a3..b33c8d0 100644
--- a/pkg/analysis_server/pubspec.yaml
+++ b/pkg/analysis_server/pubspec.yaml
@@ -8,6 +8,7 @@
   analyzer: any
   analyzer_plugin: any
   args: any
+  charcode: any
   convert: any
   crypto: any
   dart_style: any
@@ -16,6 +17,7 @@
   logging: any
   meta: any
   nnbd_migration: any
+  pub_semver: any
   source_span: any
   stream_channel: any
   package_config: any
diff --git a/pkg/analysis_server/test/domain_edit_dartfix_test.dart b/pkg/analysis_server/test/domain_edit_dartfix_test.dart
index c493bf4..7cff0de 100644
--- a/pkg/analysis_server/test/domain_edit_dartfix_test.dart
+++ b/pkg/analysis_server/test/domain_edit_dartfix_test.dart
@@ -175,72 +175,74 @@
         throwsA(TypeMatcher<StateError>()));
   }
 
-  Future<void> test_nonNullable_analysisOptions_created() async {
-    // Add pubspec for nnbd migration to detect
-    newFile('/project/pubspec.yaml', content: '''
-name: testnnbd
-''');
+  Future<void> test_nonNullable_pubspec_environmentAdded() async {
+    var originalContent = '''
+name: foo
+''';
+    newFile('/project/pubspec.yaml', content: originalContent);
     createProject();
     var result = await performFix(includedFixes: ['non-nullable']);
     expect(result.suggestions.length, greaterThanOrEqualTo(1));
     expect(result.hasErrors, isFalse);
     expect(result.edits, hasLength(1));
-    expectFileEdits('', result.edits[0], '''
-analyzer:
-  enable-experiment:
-    - non-nullable
+    expectFileEdits(originalContent, result.edits[0], '''
+environment:
+  sdk: '>=2.8.0 <3.0.0'
 
+name: foo
 ''');
   }
 
-  Future<void> test_nonNullable_analysisOptions_experimentsAdded() async {
-    var originalOptions = '''
-analyzer:
-  something:
-    - other
-
-linter:
-  - boo
+  Future<void> test_nonNullable_pubspec_sdkAdded() async {
+    var originalContent = '''
+name: foo
+environment:
+  x: y
 ''';
-    newFile('/project/analysis_options.yaml', content: originalOptions);
+    newFile('/project/pubspec.yaml', content: originalContent);
     createProject();
     var result = await performFix(includedFixes: ['non-nullable']);
     expect(result.suggestions.length, greaterThanOrEqualTo(1));
     expect(result.hasErrors, isFalse);
     expect(result.edits, hasLength(1));
-    expectFileEdits(originalOptions, result.edits[0], '''
-analyzer:
-  something:
-    - other
-  enable-experiment:
-    - non-nullable
-
-linter:
-  - boo
+    expectFileEdits(originalContent, result.edits[0], '''
+name: foo
+environment:
+  x: y
+  sdk: '>=2.8.0 <3.0.0'
 ''');
   }
 
-  Future<void> test_nonNullable_analysisOptions_nnbdAdded() async {
-    var originalOptions = '''
-analyzer:
-  enable-experiment:
-    - other
-linter:
-  - boo
+  Future<void> test_nonNullable_pubspec_sdkNotUpdated() async {
+    var originalContent = '''
+name: foo
+environment:
+  sdk: '>=2.8.0 <3.0.0'
 ''';
-    newFile('/project/analysis_options.yaml', content: originalOptions);
+    newFile('/project/pubspec.yaml', content: originalContent);
+    createProject();
+    var result = await performFix(includedFixes: ['non-nullable']);
+    expect(result.suggestions, isEmpty);
+    expect(result.hasErrors, isFalse);
+    expect(result.edits, isEmpty);
+  }
+
+  Future<void> test_nonNullable_pubspec_sdkUpdated() async {
+    var originalContent = '''
+name: foo
+environment:
+  sdk: '>=2.7.0 <3.0.0'
+''';
+    newFile('/project/pubspec.yaml', content: originalContent);
     createProject();
     var result = await performFix(includedFixes: ['non-nullable']);
     expect(result.suggestions.length, greaterThanOrEqualTo(1));
     expect(result.hasErrors, isFalse);
     expect(result.edits, hasLength(1));
-    expectFileEdits(originalOptions, result.edits[0], '''
-analyzer:
-  enable-experiment:
-    - other
-    - non-nullable
-linter:
-  - boo
+    expectFileEdits(originalContent, result.edits[0], '''
+name: foo
+environment:
+  sdk: '>=2.8.0 <3.0.0'
 ''');
   }
 
diff --git a/pkg/analysis_server/test/services/completion/dart/relevance/completion_relevance.dart b/pkg/analysis_server/test/services/completion/dart/relevance/completion_relevance.dart
index 15e904c..c46ba90 100644
--- a/pkg/analysis_server/test/services/completion/dart/relevance/completion_relevance.dart
+++ b/pkg/analysis_server/test/services/completion/dart/relevance/completion_relevance.dart
@@ -28,7 +28,19 @@
     var previous = suggestions[0];
     for (var i = 1; i < length; i++) {
       var current = suggestions[i];
-      expect(current.relevance, lessThan(previous.relevance));
+      if (current.relevance >= previous.relevance) {
+        suggestions.sort(
+            (first, second) => second.relevance.compareTo(first.relevance));
+        var buffer = StringBuffer();
+        buffer.write('Suggestions are not in the expected order. ');
+        buffer.writeln('To accept the current state, use');
+        buffer.writeln();
+        for (var suggestion in suggestions) {
+          var completion = suggestion.completion;
+          buffer.writeln("  suggestionWith(completion: '$completion'),");
+        }
+        fail(buffer.toString());
+      }
       previous = current;
     }
   }
diff --git a/pkg/analysis_server/test/services/completion/dart/relevance/static_member_relevance_test.dart b/pkg/analysis_server/test/services/completion/dart/relevance/static_member_relevance_test.dart
index e16d539..d3a952e 100644
--- a/pkg/analysis_server/test/services/completion/dart/relevance/static_member_relevance_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/relevance/static_member_relevance_test.dart
@@ -41,6 +41,27 @@
     ]);
   }
 
+  Future<void> test_elementKind() async {
+    await addTestFile('''
+class C {
+  static int f = 0;
+  static void get g {}
+  static set s(int x) {}
+  static void m() {}
+}
+
+void g() {
+  C.^
+}
+''');
+    assertOrder([
+      suggestionWith(completion: 'g'),
+      suggestionWith(completion: 's'),
+      suggestionWith(completion: 'm'),
+      suggestionWith(completion: 'f'),
+    ]);
+  }
+
   Future<void> test_hasDeprecated() async {
     await addTestFile('''
 class C {
diff --git a/pkg/analysis_server/test/src/edit/nnbd_migration/info_builder_test.dart b/pkg/analysis_server/test/src/edit/nnbd_migration/info_builder_test.dart
index 9d4a299..17cb33b 100644
--- a/pkg/analysis_server/test/src/edit/nnbd_migration/info_builder_test.dart
+++ b/pkg/analysis_server/test/src/edit/nnbd_migration/info_builder_test.dart
@@ -236,7 +236,7 @@
 @reflectiveTest
 class InfoBuilderTest extends NnbdMigrationTestBase {
   /// Assert various properties of the given [edit].
-  void assertEdit(
+  bool assertEdit(
       {@required EditDetail edit, int offset, int length, String replacement}) {
     expect(edit, isNotNull);
     if (offset != null) {
@@ -248,6 +248,7 @@
     if (replacement != null) {
       expect(edit.replacement, replacement);
     }
+    return true;
   }
 
   void assertTraceEntry(UnitInfo unit, TraceEntryInfo entryInfo,
@@ -270,6 +271,28 @@
         .toList();
   }
 
+  Future<void> test_addLate_dueToHint() async {
+    var content = '/*late*/ int x = 0;';
+    var migratedContent = '/*late*/ int  x = 0;';
+    var unit = await buildInfoForSingleTestFile(content,
+        migratedContent: migratedContent);
+    var regions = unit.fixRegions;
+    expect(regions, hasLength(2));
+    var textToRemove = '/*late*/ ';
+    assertRegionPair(regions, 0,
+        offset1: migratedContent.indexOf('/*'),
+        length1: 2,
+        offset2: migratedContent.indexOf('*/'),
+        length2: 2,
+        explanation: 'Added a late keyword, due to a hint',
+        kind: NullabilityFixKind.addLateDueToHint,
+        edits: (edits) => assertEdit(
+            edit: edits.single,
+            offset: content.indexOf(textToRemove),
+            length: textToRemove.length,
+            replacement: ''));
+  }
+
   Future<void> test_discardCondition() async {
     var unit = await buildInfoForSingleTestFile('''
 void g(int i) {
@@ -298,7 +321,7 @@
 
   Future<void> test_downcast_nonNullable() async {
     var content = 'int/*!*/ f(num/*!*/ n) => n;';
-    var migratedContent = 'int /*!*/ f(num /*!*/ n) => n as int;';
+    var migratedContent = 'int/*!*/ f(num/*!*/ n) => n as int;';
     var unit = await buildInfoForSingleTestFile(content,
         migratedContent: migratedContent);
     var regions = getNonInformativeRegions(unit.regions);
@@ -318,7 +341,7 @@
     var content = 'int/*?*/ f(num/*!*/ n) => n;';
     // TODO(paulberry): we should actually cast to `int`, not `int?`, because we
     // know `n` is non-nullable.
-    var migratedContent = 'int?/*?*/ f(num /*!*/ n) => n as int?;';
+    var migratedContent = 'int/*?*/ f(num/*!*/ n) => n as int?;';
     var unit = await buildInfoForSingleTestFile(content,
         migratedContent: migratedContent);
     var regions = getNonInformativeRegions(unit.regions);
@@ -339,7 +362,7 @@
 
   Future<void> test_downcast_nullable() async {
     var content = 'int/*?*/ f(num/*?*/ n) => n;';
-    var migratedContent = 'int?/*?*/ f(num?/*?*/ n) => n as int?;';
+    var migratedContent = 'int/*?*/ f(num/*?*/ n) => n as int?;';
     var unit = await buildInfoForSingleTestFile(content,
         migratedContent: migratedContent);
     var regions = getNonInformativeRegions(unit.regions);
@@ -357,7 +380,7 @@
 
   Future<void> test_downcast_nullable_to_nonNullable() async {
     var content = 'int/*!*/ f(num/*?*/ n) => n;';
-    var migratedContent = 'int /*!*/ f(num?/*?*/ n) => n as int;';
+    var migratedContent = 'int/*!*/ f(num/*?*/ n) => n as int;';
     var unit = await buildInfoForSingleTestFile(content,
         migratedContent: migratedContent);
     var regions = getNonInformativeRegions(unit.regions);
@@ -376,7 +399,7 @@
   Future<void> test_downcast_with_traces() async {
     var content = 'List<int/*!*/>/*!*/ f(List<int/*?*/>/*?*/ x) => x;';
     var migratedContent =
-        'List<int /*!*/> /*!*/ f(List<int?/*?*/>?/*?*/ x) => x as List<int>;';
+        'List<int/*!*/>/*!*/ f(List<int/*?*/>/*?*/ x) => x as List<int>;';
     var unit = await buildInfoForSingleTestFile(content,
         migratedContent: migratedContent);
     var regions = unit.regions.where(
@@ -517,12 +540,12 @@
 class C {
   C? operator+(C  c) => null;
 }
-C /*!*/ _f(C  c) => (c + c)!;
+C/*!*/ _f(C  c) => (c + c)!;
 ''';
     var unit = await buildInfoForSingleTestFile(originalContent,
         migratedContent: migratedContent);
     var regions = unit.fixRegions;
-    expect(regions, hasLength(2));
+    expect(regions, hasLength(3));
     assertRegion(
         region: regions[0],
         offset: migratedContent.indexOf('? operator'),
@@ -530,6 +553,12 @@
         explanation: "Changed type 'C' to be nullable");
     assertRegion(
         region: regions[1],
+        offset: migratedContent.indexOf('/*!*/'),
+        length: 5,
+        explanation: "Type 'C' was not made nullable due to a hint",
+        kind: NullabilityFixKind.typeNotMadeNullableDueToHint);
+    assertRegion(
+        region: regions[2],
         offset: migratedContent.indexOf('!;'),
         length: 1,
         explanation: 'Added a non-null assertion to nullable expression',
@@ -538,26 +567,27 @@
 
   Future<void> test_nullCheck_dueToHint() async {
     var content = 'int f(int/*?*/ x) => x/*!*/;';
-    var migratedContent = 'int  f(int?/*?*/ x) => x!/*!*/;';
+    var migratedContent = 'int  f(int/*?*/ x) => x/*!*/;';
     var unit = await buildInfoForSingleTestFile(content,
         migratedContent: migratedContent);
     var regions = unit.fixRegions;
-    expect(regions, hasLength(2));
-    // regions[0] is `int?`.
-    var region = regions[1];
-    assertRegion(
-        region: region,
-        offset: migratedContent.indexOf('!/*!*/'),
+    expect(regions, hasLength(4));
+    assertRegionPair(regions, 0,
+        kind: NullabilityFixKind.makeTypeNullableDueToHint);
+    var hintText = '/*!*/';
+    assertRegionPair(regions, 2,
+        offset1: migratedContent.indexOf(hintText),
+        length1: 2,
+        offset2: migratedContent.indexOf(hintText) + 3,
+        length2: 2,
         explanation: 'Accepted a null check hint',
-        kind: NullabilityFixKind.checkExpressionDueToHint);
-    // Note that traces are still included.
-    expect(region.traces, isNotEmpty);
-    var textToRemove = '/*!*/';
-    assertEdit(
-        edit: region.edits.single,
-        offset: content.indexOf(textToRemove),
-        length: textToRemove.length,
-        replacement: '');
+        kind: NullabilityFixKind.checkExpressionDueToHint,
+        traces: isNotEmpty,
+        edits: ((edits) => assertEdit(
+            edit: edits.single,
+            offset: content.indexOf(hintText),
+            length: hintText.length,
+            replacement: '')));
   }
 
   Future<void> test_nullCheck_onMemberAccess() async {
@@ -642,7 +672,7 @@
     // Note: even though `as int` is removed, it still shows up in the
     // preview, since we show deleted text.
     var migratedContent = '''
-void f(num  n, int?/*?*/ i) {
+void f(num  n, int/*?*/ i) {
   if (n is! int ) return;
   print((n as int).isEven);
   print(i! + 1);
@@ -651,16 +681,17 @@
     var unit = await buildInfoForSingleTestFile(originalContent,
         migratedContent: migratedContent, removeViaComments: false);
     var regions = unit.fixRegions;
-    expect(regions, hasLength(3));
-    // regions[0] is the addition of `?` to the type of `i`.
+    expect(regions, hasLength(4));
+    assertRegionPair(regions, 0,
+        kind: NullabilityFixKind.makeTypeNullableDueToHint);
     assertRegion(
-        region: regions[1],
+        region: regions[2],
         offset: migratedContent.indexOf(' as int'),
         length: ' as int'.length,
         explanation: 'Discarded a downcast that is now unnecessary',
         kind: NullabilityFixKind.removeAs);
     assertRegion(
-        region: regions[2],
+        region: regions[3],
         offset: migratedContent.indexOf('! + 1'),
         explanation: 'Added a non-null assertion to nullable expression',
         kind: NullabilityFixKind.checkExpression);
@@ -718,7 +749,7 @@
   if (i == null) return;
 }
 ''', migratedContent: '''
-void f(int /*!*/ i) {
+void f(int/*!*/ i) {
   /* if (i == null) return; */
 }
 ''');
@@ -797,7 +828,7 @@
 
   Future<void> test_trace_nullCheck() async {
     var unit = await buildInfoForSingleTestFile('int f(int/*?*/ i) => i + 1;',
-        migratedContent: 'int  f(int?/*?*/ i) => i! + 1;');
+        migratedContent: 'int  f(int/*?*/ i) => i! + 1;');
     var region = unit.regions
         .where((regionInfo) => regionInfo.offset == unit.content.indexOf('! +'))
         .single;
@@ -808,12 +839,16 @@
     expect(entries, hasLength(2));
     // Entry 0 is the nullability of the type of i.
     // TODO(paulberry): -1 is a bug.
-    assertTraceEntry(unit, entries[0], 'f', unit.content.indexOf('int?') - 1,
-        contains('parameter 0 of f'));
+    assertTraceEntry(unit, entries[0], 'f',
+        unit.content.indexOf('int/*?*/') - 1, contains('parameter 0 of f'));
     // Entry 1 is the edge from always to the type of i.
     // TODO(paulberry): this edge provides no additional useful information and
     // shouldn't be included in the trace.
-    assertTraceEntry(unit, entries[1], 'f', unit.content.indexOf('int?') - 1,
+    assertTraceEntry(
+        unit,
+        entries[1],
+        'f',
+        unit.content.indexOf('int/*?*/') - 1,
         'explicitly hinted to be nullable');
   }
 
@@ -835,7 +870,7 @@
 void g(int  i) { // g
   f(i); // call f
 }
-void h(int?/*?*/ i) {
+void h(int/*?*/ i) {
   g(i!);
 }
 ''');
@@ -872,18 +907,18 @@
 
   Future<void> test_trace_nullCheckHint() async {
     var unit = await buildInfoForSingleTestFile('int f(int/*?*/ i) => i/*!*/;',
-        migratedContent: 'int  f(int?/*?*/ i) => i!/*!*/;');
+        migratedContent: 'int  f(int/*?*/ i) => i/*!*/;');
     var region = unit.regions
         .where(
-            (regionInfo) => regionInfo.offset == unit.content.indexOf('!/*!*/'))
+            (regionInfo) => regionInfo.offset == unit.content.indexOf('/*!*/'))
         .single;
     expect(region.traces, hasLength(1));
     var trace = region.traces.single;
     expect(trace.description, 'Reason');
     expect(trace.entries, hasLength(1));
-    // TODO(paulberry): -2 is a bug.
+    // TODO(paulberry): -1 is a bug.
     assertTraceEntry(unit, trace.entries.single, 'f',
-        unit.content.indexOf('i!/*!*/') - 2, 'Null check hint');
+        unit.content.indexOf('i/*!*/') - 1, 'Null check hint');
   }
 
   Future<void> test_trace_substitutionNode() async {
@@ -895,12 +930,12 @@
 Map<int, String> x = {};
 String/*!*/ y = x[0];
 ''', migratedContent: '''
-class C<T extends Object /*!*/> {}
+class C<T extends Object/*!*/> {}
 
-C<int? /*?*/ >? c;
+C<int /*?*/ >? c;
 
 Map<int , String >  x = {};
-String /*!*/ y = x[0]!;
+String/*!*/ y = x[0]!;
 ''');
     var region = unit.regions
         .where((regionInfo) => regionInfo.offset == unit.content.indexOf('!;'))
@@ -930,32 +965,35 @@
 
   Future<void> test_type_made_nullable_due_to_hint() async {
     var content = 'int/*?*/ x = 0;';
-    var migratedContent = 'int?/*?*/ x = 0;';
+    var migratedContent = 'int/*?*/ x = 0;';
     var unit = await buildInfoForSingleTestFile(content,
         migratedContent: migratedContent);
-    var region = unit.fixRegions.single;
-    assertRegion(
-        region: region,
-        offset: migratedContent.indexOf('?/*?*/'),
+    var regions = unit.fixRegions;
+    expect(regions, hasLength(2));
+    var textToRemove = '/*?*/';
+    assertRegionPair(regions, 0,
+        offset1: migratedContent.indexOf(textToRemove),
+        length1: 2,
+        offset2: migratedContent.indexOf(textToRemove) + 3,
+        length2: 2,
         explanation:
             "Changed type 'int' to be nullable, due to a nullability hint",
-        kind: NullabilityFixKind.makeTypeNullableDueToHint);
-    // Note that traces are still included.
-    expect(region.traces, isNotNull);
-    var textToRemove = '/*?*/';
-    var edits = region.edits;
-    expect(edits, hasLength(2));
-    var editsByDescription = {for (var edit in edits) edit.description: edit};
-    assertEdit(
-        edit: editsByDescription['Add /*!*/ hint'],
-        offset: content.indexOf(textToRemove),
-        length: textToRemove.length,
-        replacement: '/*!*/');
-    assertEdit(
-        edit: editsByDescription['Remove /*?*/ hint'],
-        offset: content.indexOf(textToRemove),
-        length: textToRemove.length,
-        replacement: '');
+        kind: NullabilityFixKind.makeTypeNullableDueToHint,
+        traces: isNotNull, edits: (List<EditDetail> edits) {
+      expect(edits, hasLength(2));
+      var editsByDescription = {for (var edit in edits) edit.description: edit};
+      assertEdit(
+          edit: editsByDescription['Change to /*!*/ hint'],
+          offset: content.indexOf(textToRemove),
+          length: textToRemove.length,
+          replacement: '/*!*/');
+      assertEdit(
+          edit: editsByDescription['Remove /*?*/ hint'],
+          offset: content.indexOf(textToRemove),
+          length: textToRemove.length,
+          replacement: '');
+      return true;
+    });
   }
 
   Future<void> test_type_not_made_nullable() async {
@@ -975,33 +1013,35 @@
 
   Future<void> test_type_not_made_nullable_due_to_hint() async {
     var content = 'int/*!*/ i = 0;';
-    var migratedContent = 'int /*!*/ i = 0;';
+    var migratedContent = 'int/*!*/ i = 0;';
     var unit = await buildInfoForSingleTestFile(content,
         migratedContent: migratedContent);
-    var region = unit.regions
-        .where((regionInfo) =>
-            regionInfo.offset == migratedContent.indexOf(' /*!*/ i'))
-        .single;
-    assertRegion(
-        region: region,
-        offset: migratedContent.indexOf(' /*!*/ i'),
-        explanation: "Type 'int' was not made nullable due to a hint",
-        kind: NullabilityFixKind.typeNotMadeNullableDueToHint);
-    // Note that traces are still included.
-    expect(region.traces, isNotNull);
+    var regions = unit.regions;
+    expect(regions, hasLength(1));
     var textToRemove = '/*!*/';
-    var edits = region.edits;
-    expect(edits, hasLength(2));
-    var editsByDescription = {for (var edit in edits) edit.description: edit};
-    assertEdit(
-        edit: editsByDescription['Add /*?*/ hint'],
-        offset: content.indexOf(textToRemove),
-        length: textToRemove.length,
-        replacement: '/*?*/');
-    assertEdit(
-        edit: editsByDescription['Remove /*!*/ hint'],
-        offset: content.indexOf(textToRemove),
-        length: textToRemove.length,
-        replacement: '');
+    assertRegion(
+        region: regions[0],
+        offset: migratedContent.indexOf(textToRemove),
+        length: 5,
+        explanation: "Type 'int' was not made nullable due to a hint",
+        kind: NullabilityFixKind.typeNotMadeNullableDueToHint,
+        traces: isNotNull,
+        edits: (List<EditDetail> edits) {
+          expect(edits, hasLength(2));
+          var editsByDescription = {
+            for (var edit in edits) edit.description: edit
+          };
+          assertEdit(
+              edit: editsByDescription['Change to /*?*/ hint'],
+              offset: content.indexOf(textToRemove),
+              length: textToRemove.length,
+              replacement: '/*?*/');
+          assertEdit(
+              edit: editsByDescription['Remove /*!*/ hint'],
+              offset: content.indexOf(textToRemove),
+              length: textToRemove.length,
+              replacement: '');
+          return true;
+        });
   }
 }
diff --git a/pkg/analysis_server/test/src/edit/nnbd_migration/nnbd_migration_test_base.dart b/pkg/analysis_server/test/src/edit/nnbd_migration/nnbd_migration_test_base.dart
index b3e1c6b..d8a281a 100644
--- a/pkg/analysis_server/test/src/edit/nnbd_migration/nnbd_migration_test_base.dart
+++ b/pkg/analysis_server/test/src/edit/nnbd_migration/nnbd_migration_test_base.dart
@@ -50,7 +50,7 @@
       Object explanation = anything,
       Object edits = anything,
       Object traces = anything,
-      NullabilityFixKind kind = NullabilityFixKind.makeTypeNullable}) {
+      Object kind = NullabilityFixKind.makeTypeNullable}) {
     if (offset != null) {
       expect(region.offset, offset);
       expect(region.length, length ?? 1);
@@ -61,6 +61,36 @@
     expect(region.traces, traces);
   }
 
+  /// Asserts various properties of the pair of [regions], `regions[index]` and
+  /// `regions[index + 1]`.  The expected offsets and lengths are specified
+  /// separately; everything else is asserted using the same matcher.
+  void assertRegionPair(List<RegionInfo> regions, int index,
+      {int offset1,
+      int length1,
+      int offset2,
+      int length2,
+      Object explanation = anything,
+      Object edits = anything,
+      Object traces = anything,
+      Object kind = anything}) {
+    assertRegion(
+        region: regions[index],
+        offset: offset1,
+        length: length1,
+        explanation: explanation,
+        edits: edits,
+        traces: traces,
+        kind: kind);
+    assertRegion(
+        region: regions[index + 1],
+        offset: offset2,
+        length: length2,
+        explanation: explanation,
+        edits: edits,
+        traces: traces,
+        kind: kind);
+  }
+
   /// Uses the InfoBuilder to build information for [testFile].
   ///
   /// The information is stored in [infos].
diff --git a/pkg/analysis_server/test/src/edit/nnbd_migration/unit_renderer_test.dart b/pkg/analysis_server/test/src/edit/nnbd_migration/unit_renderer_test.dart
index a861fd6..5ab9abf 100644
--- a/pkg/analysis_server/test/src/edit/nnbd_migration/unit_renderer_test.dart
+++ b/pkg/analysis_server/test/src/edit/nnbd_migration/unit_renderer_test.dart
@@ -77,6 +77,26 @@
         equals('Added a non-null assertion to nullable expression'));
   }
 
+  Future<void> test_editList_countsHintAcceptanceSingly() async {
+    await buildInfoForSingleTestFile('int f(int/*?*/ x) => x/*!*/;',
+        migratedContent: 'int  f(int/*?*/ x) => x/*!*/;');
+    var output = renderUnits()[0];
+    expect(
+        output.edits.keys,
+        unorderedEquals([
+          '1 null check hint converted to null check',
+          '1 nullability hint converted to ?'
+        ]));
+  }
+
+  Future<void> test_editList_countsHintAcceptanceSingly_late() async {
+    await buildInfoForSingleTestFile('/*late*/ int x = 0;',
+        migratedContent: '/*late*/ int  x = 0;');
+    var output = renderUnits()[0];
+    expect(output.edits.keys,
+        unorderedEquals(['1 late hint converted to late keyword']));
+  }
+
   Future<void> test_editList_pluralHeader() async {
     await buildInfoForSingleTestFile('''
 int a = null;
diff --git a/pkg/analyzer/lib/src/summary/format.dart b/pkg/analyzer/lib/src/summary/format.dart
index 0a6a1df..a97325c 100644
--- a/pkg/analyzer/lib/src/summary/format.dart
+++ b/pkg/analyzer/lib/src/summary/format.dart
@@ -16973,10 +16973,18 @@
 class LinkedNodeTypeSubstitutionBuilder extends Object
     with _LinkedNodeTypeSubstitutionMixin
     implements idl.LinkedNodeTypeSubstitution {
+  bool _isLegacy;
   List<LinkedNodeTypeBuilder> _typeArguments;
   List<int> _typeParameters;
 
   @override
+  bool get isLegacy => _isLegacy ??= false;
+
+  set isLegacy(bool value) {
+    this._isLegacy = value;
+  }
+
+  @override
   List<LinkedNodeTypeBuilder> get typeArguments =>
       _typeArguments ??= <LinkedNodeTypeBuilder>[];
 
@@ -16993,8 +17001,11 @@
   }
 
   LinkedNodeTypeSubstitutionBuilder(
-      {List<LinkedNodeTypeBuilder> typeArguments, List<int> typeParameters})
-      : _typeArguments = typeArguments,
+      {bool isLegacy,
+      List<LinkedNodeTypeBuilder> typeArguments,
+      List<int> typeParameters})
+      : _isLegacy = isLegacy,
+        _typeArguments = typeArguments,
         _typeParameters = typeParameters;
 
   /// Flush [informative] data recursively.
@@ -17020,6 +17031,7 @@
         x?.collectApiSignature(signature);
       }
     }
+    signature.addBool(this._isLegacy == true);
   }
 
   fb.Offset finish(fb.Builder fbBuilder) {
@@ -17033,6 +17045,9 @@
       offset_typeParameters = fbBuilder.writeListUint32(_typeParameters);
     }
     fbBuilder.startTable();
+    if (_isLegacy == true) {
+      fbBuilder.addBool(2, true);
+    }
     if (offset_typeArguments != null) {
       fbBuilder.addOffset(1, offset_typeArguments);
     }
@@ -17061,10 +17076,17 @@
 
   _LinkedNodeTypeSubstitutionImpl(this._bc, this._bcOffset);
 
+  bool _isLegacy;
   List<idl.LinkedNodeType> _typeArguments;
   List<int> _typeParameters;
 
   @override
+  bool get isLegacy {
+    _isLegacy ??= const fb.BoolReader().vTableGet(_bc, _bcOffset, 2, false);
+    return _isLegacy;
+  }
+
+  @override
   List<idl.LinkedNodeType> get typeArguments {
     _typeArguments ??=
         const fb.ListReader<idl.LinkedNodeType>(_LinkedNodeTypeReader())
@@ -17085,6 +17107,9 @@
   @override
   Map<String, Object> toJson() {
     Map<String, Object> _result = <String, Object>{};
+    if (isLegacy != false) {
+      _result["isLegacy"] = isLegacy;
+    }
     if (typeArguments.isNotEmpty) {
       _result["typeArguments"] =
           typeArguments.map((_value) => _value.toJson()).toList();
@@ -17097,6 +17122,7 @@
 
   @override
   Map<String, Object> toMap() => {
+        "isLegacy": isLegacy,
         "typeArguments": typeArguments,
         "typeParameters": typeParameters,
       };
diff --git a/pkg/analyzer/lib/src/summary/format.fbs b/pkg/analyzer/lib/src/summary/format.fbs
index 7f390d5..cfd13d9 100644
--- a/pkg/analyzer/lib/src/summary/format.fbs
+++ b/pkg/analyzer/lib/src/summary/format.fbs
@@ -1219,6 +1219,8 @@
 
 /// Information about a type substitution.
 table LinkedNodeTypeSubstitution {
+  isLegacy:bool (id: 2);
+
   typeArguments:[LinkedNodeType] (id: 1);
 
   typeParameters:[uint] (id: 0);
diff --git a/pkg/analyzer/lib/src/summary/idl.dart b/pkg/analyzer/lib/src/summary/idl.dart
index 2175ef2..6e182ec 100644
--- a/pkg/analyzer/lib/src/summary/idl.dart
+++ b/pkg/analyzer/lib/src/summary/idl.dart
@@ -1839,6 +1839,9 @@
 
 /// Information about a type substitution.
 abstract class LinkedNodeTypeSubstitution extends base.SummaryClass {
+  @Id(2)
+  bool get isLegacy;
+
   @Id(1)
   List<LinkedNodeType> get typeArguments;
 
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
index 09fd0b0..c36c2ed 100644
--- a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
@@ -107,7 +107,12 @@
     var typeArguments = substitutionNode.typeArguments.map(_readType).toList();
     var substitution = Substitution.fromPairs(typeParameters, typeArguments);
 
-    return ExecutableMember.from2(element, substitution);
+    var member = ExecutableMember.from2(element, substitution);
+    if (substitutionNode.isLegacy) {
+      member = Member.legacy(member);
+    }
+
+    return member;
   }
 
   T _getElement<T extends Element>(int index) {
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart b/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart
index 4d0c8bf..497e564 100644
--- a/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart
@@ -1478,6 +1478,7 @@
       var elementIndex = _indexOfElement(element.declaration);
       var substitution = element.substitution.map;
       var substitutionBuilder = LinkedNodeTypeSubstitutionBuilder(
+        isLegacy: element.isLegacy,
         typeParameters: substitution.keys.map(_indexOfElement).toList(),
         typeArguments: substitution.values.map(_writeType).toList(),
       );
diff --git a/pkg/analyzer/test/src/dart/resolution/constant_test.dart b/pkg/analyzer/test/src/dart/resolution/constant_test.dart
index 2ad2114..25c3b3d 100644
--- a/pkg/analyzer/test/src/dart/resolution/constant_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/constant_test.dart
@@ -328,6 +328,24 @@
     _assertIntValue(c, 42);
   }
 
+  test_topLevelVariable_optOut3() async {
+    newFile('/test/lib/a.dart', content: r'''
+// @dart = 2.7
+const a = int.fromEnvironment('a', defaultValue: 42);
+''');
+
+    await assertNoErrorsInCode(r'''
+// @dart = 2.7
+import 'a.dart';
+
+const b = a;
+''');
+
+    var c = findElement.topVar('b');
+    assertType(c.type, 'int*');
+    _assertIntValue(c, 42);
+  }
+
   void _assertIntValue(VariableElement element, int value) {
     expect(element.constantValue.toIntValue(), value);
   }
diff --git a/pkg/analyzer_plugin/lib/src/utilities/completion/optype.dart b/pkg/analyzer_plugin/lib/src/utilities/completion/optype.dart
index e06795d..2887b49 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/completion/optype.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/completion/optype.dart
@@ -1064,6 +1064,7 @@
       } else if (node.parent is Annotation) {
         optype.includeConstructorSuggestions = true;
       } else {
+        optype.completionLocation = 'PropertyAccess_propertyName';
         optype.includeReturnValueSuggestions = true;
         optype.includeTypeNameSuggestions = true;
         optype.includeVoidReturnSuggestions =
diff --git a/pkg/analyzer_plugin/lib/utilities/assist/assist.dart b/pkg/analyzer_plugin/lib/utilities/assist/assist.dart
index 00dedd3..d6fa2a4 100644
--- a/pkg/analyzer_plugin/lib/utilities/assist/assist.dart
+++ b/pkg/analyzer_plugin/lib/utilities/assist/assist.dart
@@ -65,7 +65,8 @@
 ///
 /// Clients may not extend, implement or mix-in this class.
 class AssistKind {
-  /// The unique identifier of this kind of assist.
+  /// The unique identifier of this kind of assist. May be used by client editors,
+  /// for example to allow key-binding specific fixes (or groups of).
   final String id;
 
   /// The priority of this kind of assist for the kind of error being addressed.
diff --git a/pkg/analyzer_plugin/lib/utilities/fixes/fixes.dart b/pkg/analyzer_plugin/lib/utilities/fixes/fixes.dart
index ac3c5fe..46c3a7d 100644
--- a/pkg/analyzer_plugin/lib/utilities/fixes/fixes.dart
+++ b/pkg/analyzer_plugin/lib/utilities/fixes/fixes.dart
@@ -91,8 +91,9 @@
 ///
 /// Clients may not extend, implement or mix-in this class.
 class FixKind {
-  /// The name of this kind of fix, used for debugging.
-  final String name;
+  /// The unique identifier of this kind of assist. May be used by client editors,
+  /// for example to allow key-binding specific fixes (or groups of).
+  final String id;
 
   /// The priority of this kind of fix for the kind of error being addressed
   /// where a higher integer value indicates a higher priority and relevance.
@@ -108,21 +109,21 @@
   /// kind of 'applied together' fix.
   final String appliedTogetherMessage;
 
-  /// Initialize a newly created kind of fix to have the given [name],
+  /// Initialize a newly created kind of fix to have the given [id],
   /// [priority], [message], and optionally [canBeAppliedTogether] and
   /// [appliedTogetherMessage].
-  const FixKind(this.name, this.priority, this.message,
+  const FixKind(this.id, this.priority, this.message,
       {this.appliedTogetherMessage});
 
   @override
-  int get hashCode => name.hashCode;
+  int get hashCode => id.hashCode;
 
   @override
-  bool operator ==(o) => o is FixKind && o.name == name;
+  bool operator ==(o) => o is FixKind && o.id == id;
 
   /// The change can be made with other fixes of this [FixKind].
   bool canBeAppliedTogether() => appliedTogetherMessage != null;
 
   @override
-  String toString() => name;
+  String toString() => id;
 }
diff --git a/pkg/analyzer_plugin/test/src/utilities/completion/optype_test.dart b/pkg/analyzer_plugin/test/src/utilities/completion/optype_test.dart
index 7292c68..1613b9d 100644
--- a/pkg/analyzer_plugin/test/src/utilities/completion/optype_test.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/completion/optype_test.dart
@@ -271,7 +271,11 @@
     // SimpleIdentifier  PrefixedIdentifier  ArgumentList
     addTestSource('void main() {expect(aa.^)}');
     await assertOpType(
-        constructors: true, returnValue: true, typeNames: true, prefixed: true);
+        completionLocation: 'PropertyAccess_propertyName',
+        constructors: true,
+        prefixed: true,
+        returnValue: true,
+        typeNames: true);
   }
 
   Future<void> test_argumentList_resolved() async {
@@ -614,6 +618,7 @@
   Future<void> test_block_keyword() async {
     addTestSource('class C { static C get instance => null; } main() {C.in^}');
     await assertOpType(
+        completionLocation: 'PropertyAccess_propertyName',
         constructors: true,
         prefixed: true,
         returnValue: true,
@@ -1194,33 +1199,33 @@
     // FormalParameterList MethodDeclaration
     addTestSource('class A {a(b.^ f) { }}');
     await assertOpType(
-      constructors: true,
-      returnValue: true,
-      typeNames: true,
-      prefixed: true,
-    );
+        completionLocation: 'PropertyAccess_propertyName',
+        constructors: true,
+        prefixed: true,
+        returnValue: true,
+        typeNames: true);
   }
 
   Future<void> test_formalParameter_partialType2() async {
     // FormalParameterList MethodDeclaration
     addTestSource('class A {a(b.z^ f) { }}');
     await assertOpType(
-      constructors: true,
-      returnValue: true,
-      typeNames: true,
-      prefixed: true,
-    );
+        completionLocation: 'PropertyAccess_propertyName',
+        constructors: true,
+        prefixed: true,
+        returnValue: true,
+        typeNames: true);
   }
 
   Future<void> test_formalParameter_partialType3() async {
     // FormalParameterList MethodDeclaration
     addTestSource('class A {a(b.^) { }}');
     await assertOpType(
-      constructors: true,
-      returnValue: true,
-      typeNames: true,
-      prefixed: true,
-    );
+        completionLocation: 'PropertyAccess_propertyName',
+        constructors: true,
+        prefixed: true,
+        returnValue: true,
+        typeNames: true);
   }
 
   Future<void> test_formalParameterList_empty() async {
@@ -1551,7 +1556,11 @@
     // SimpleIdentifier  PrefixIdentifier  IfStatement
     addTestSource('main() {var a; if (a.^) something}');
     await assertOpType(
-        constructors: true, returnValue: true, typeNames: true, prefixed: true);
+        completionLocation: 'PropertyAccess_propertyName',
+        constructors: true,
+        prefixed: true,
+        returnValue: true,
+        typeNames: true);
   }
 
   Future<void> test_implementsClause_beginning() async {
@@ -1654,7 +1663,11 @@
     // SimpleIdentifier  PrefixedIdentifier  InterpolationExpression
     addTestSource('main() {String name; print("hello \${name.^}");}');
     await assertOpType(
-        constructors: true, returnValue: true, typeNames: true, prefixed: true);
+        completionLocation: 'PropertyAccess_propertyName',
+        constructors: true,
+        prefixed: true,
+        returnValue: true,
+        typeNames: true);
   }
 
   Future<void> test_interpolationExpression_prefix_target() async {
@@ -2025,22 +2038,24 @@
     // SimpleIdentifier PrefixedIdentifier ExpressionStatement Block
     addTestSource('main() {A.^}');
     await assertOpType(
+        completionLocation: 'PropertyAccess_propertyName',
         constructors: true,
+        prefixed: true,
         returnValue: true,
         typeNames: true,
-        voidReturn: true,
-        prefixed: true);
+        voidReturn: true);
   }
 
   Future<void> test_prefixedIdentifier_class_imported() async {
     // SimpleIdentifier  PrefixedIdentifier  ExpressionStatement
     addTestSource('main() {A a; a.^}');
     await assertOpType(
+        completionLocation: 'PropertyAccess_propertyName',
         constructors: true,
+        prefixed: true,
         returnValue: true,
         typeNames: true,
-        voidReturn: true,
-        prefixed: true);
+        voidReturn: true);
   }
 
   Future<void> test_prefixedIdentifier_prefix() async {
diff --git a/pkg/async_helper/lib/async_minitest.dart b/pkg/async_helper/lib/async_minitest.dart
index cf60812..57a05eb 100644
--- a/pkg/async_helper/lib/async_minitest.dart
+++ b/pkg/async_helper/lib/async_minitest.dart
@@ -99,7 +99,7 @@
 
 dynamic expectAsync(Function f, {int count = 1}) {
   var f2 = f; // Avoid type-promoting f, we want dynamic invocations.
-  if (f2 is Function(Null, Null, Null, Null, Null)) {
+  if (f2 is Function(Never, Never, Never, Never, Never)) {
     asyncStart(count);
     return ([a, b, c, d, e]) {
       var result = f(a, b, c, d, e);
@@ -107,7 +107,7 @@
       return result;
     };
   }
-  if (f2 is Function(Null, Null, Null, Null)) {
+  if (f2 is Function(Never, Never, Never, Never)) {
     asyncStart(count);
     return ([a, b, c, d]) {
       var result = f(a, b, c, d);
@@ -115,7 +115,7 @@
       return result;
     };
   }
-  if (f2 is Function(Null, Null, Null)) {
+  if (f2 is Function(Never, Never, Never)) {
     asyncStart(count);
     return ([a, b, c]) {
       var result = f(a, b, c);
@@ -123,7 +123,7 @@
       return result;
     };
   }
-  if (f2 is Function(Null, Null)) {
+  if (f2 is Function(Never, Never)) {
     asyncStart(count);
     return ([a, b]) {
       var result = f(a, b);
@@ -131,7 +131,7 @@
       return result;
     };
   }
-  if (f2 is Function(Null)) {
+  if (f2 is Function(Never)) {
     asyncStart(count);
     return ([a]) {
       var result = f(a);
diff --git a/pkg/compiler/lib/src/dump_info.dart b/pkg/compiler/lib/src/dump_info.dart
index 646cdee..bc001f7 100644
--- a/pkg/compiler/lib/src/dump_info.dart
+++ b/pkg/compiler/lib/src/dump_info.dart
@@ -276,8 +276,10 @@
     });
     int parameterIndex = 0;
     closedWorld.elementEnvironment.forEachParameter(function, (type, name, _) {
-      parameters.add(new ParameterInfo(
-          name, inferredParameterTypes[parameterIndex++], '$type'));
+      // Synthesized parameters have no name. This can happen on parameters of
+      // setters derived from lowering late fields.
+      parameters.add(new ParameterInfo(name ?? '#t${parameterIndex}',
+          inferredParameterTypes[parameterIndex++], '$type'));
     });
 
     var functionType = environment.getFunctionType(function);
diff --git a/pkg/compiler/lib/src/js_backend/specialized_checks.dart b/pkg/compiler/lib/src/js_backend/specialized_checks.dart
index 9df9562..8651bf2 100644
--- a/pkg/compiler/lib/src/js_backend/specialized_checks.dart
+++ b/pkg/compiler/lib/src/js_backend/specialized_checks.dart
@@ -24,6 +24,20 @@
 class SpecializedChecks {
   static IsTestSpecialization findIsTestSpecialization(
       DartType dartType, HGraph graph, JClosedWorld closedWorld) {
+    if (dartType is LegacyType) {
+      DartType base = dartType.baseType;
+      // `Never*` accepts only `null`.
+      if (base is NeverType) return IsTestSpecialization.null_;
+      // TODO(sra): Handle strong checking 'x is Object' --> `x != null`.
+      // `Object*` is top and should be handled by constant folding.
+      if (base.isObject) return null;
+      return _findIsTestSpecialization(base, graph, closedWorld);
+    }
+    return _findIsTestSpecialization(dartType, graph, closedWorld);
+  }
+
+  static IsTestSpecialization _findIsTestSpecialization(
+      DartType dartType, HGraph graph, JClosedWorld closedWorld) {
     if (dartType is InterfaceType) {
       ClassEntity element = dartType.element;
       JCommonElements commonElements = closedWorld.commonElements;
diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart
index b5848269..781976a 100644
--- a/pkg/compiler/lib/src/ssa/codegen.dart
+++ b/pkg/compiler/lib/src/ssa/codegen.dart
@@ -3518,7 +3518,13 @@
         break;
 
       case IsTestSpecialization.instanceof:
-        InterfaceType type = node.dartType;
+        DartType dartType = node.dartType;
+        // We don't generate instancof specializations for Never* and Object*.
+        assert(dartType is InterfaceType ||
+            (dartType is LegacyType &&
+                !dartType.baseType.isObject &&
+                dartType.baseType is! NeverType));
+        InterfaceType type = dartType.withoutNullability;
         _registry.registerTypeUse(TypeUse.instanceConstructor(type));
         test = handleNegative(js.js('# instanceof #',
             [value, _emitter.constructorAccess(type.element)]));
diff --git a/pkg/compiler/lib/src/ssa/nodes.dart b/pkg/compiler/lib/src/ssa/nodes.dart
index 695aca3..414e7a3 100644
--- a/pkg/compiler/lib/src/ssa/nodes.dart
+++ b/pkg/compiler/lib/src/ssa/nodes.dart
@@ -4636,8 +4636,19 @@
   // is definitely false, so we reuse some of the case-by-case logic from the
   // old [HIs] optimization.
   if (closedWorld.dartTypes.isTopType(dartType)) return AbstractBool.True;
-  if (dartType is! InterfaceType) return AbstractBool.Maybe;
-  InterfaceType type = dartType;
+
+  InterfaceType type;
+  if (dartType is InterfaceType) {
+    type = dartType;
+  } else if (dartType is LegacyType) {
+    DartType base = dartType.baseType;
+    if (base is! InterfaceType) return AbstractBool.Maybe;
+    assert(!base.isObject); // Top type handled above;
+    type = base;
+  } else {
+    return AbstractBool.Maybe;
+  }
+
   ClassEntity element = type.element;
   if (type.typeArguments.isNotEmpty) return AbstractBool.Maybe;
   JCommonElements commonElements = closedWorld.commonElements;
diff --git a/pkg/frontend_server/lib/frontend_server.dart b/pkg/frontend_server/lib/frontend_server.dart
index 29106f1..6571a7a 100644
--- a/pkg/frontend_server/lib/frontend_server.dart
+++ b/pkg/frontend_server/lib/frontend_server.dart
@@ -58,6 +58,9 @@
       help:
           'Enable global type flow analysis and related transformations in AOT mode.',
       defaultsTo: false)
+  ..addFlag('tree-shake-write-only-fields',
+      help: 'Enable tree shaking of fields which are only written in AOT mode.',
+      defaultsTo: false)
   ..addFlag('protobuf-tree-shaker',
       help: 'Enable protobuf tree shaker transformation in AOT mode.',
       defaultsTo: false)
@@ -520,7 +523,8 @@
           environmentDefines: environmentDefines,
           enableAsserts: options['enable-asserts'],
           useProtobufTreeShaker: options['protobuf-tree-shaker'],
-          minimalKernel: options['minimal-kernel']));
+          minimalKernel: options['minimal-kernel'],
+          treeShakeWriteOnlyFields: options['tree-shake-write-only-fields']));
     }
     if (results.component != null) {
       transformer?.transform(results.component);
@@ -909,46 +913,45 @@
       Map<String, String> jsFrameValues,
       String moduleName,
       String expression) async {
-    final String boundaryKey = Uuid().generateV4();
-    _outputStream.writeln('result $boundaryKey');
-
     _generator.accept();
     errors.clear();
 
-    if (_bundler != null) {
-      var kernel2jsCompiler = _bundler.compilers[moduleName];
-      if (kernel2jsCompiler == null) {
-        throw Exception('Cannot find kernel2js compiler for $moduleName. '
-            'Compilers are avaiable for modules: '
-            '\n\t${_bundler.compilers.keys.toString()}');
-      }
-      assert(kernel2jsCompiler != null);
+    if (_bundler == null) {
+      reportError('JavaScript bundler is null');
+      return;
+    }
+    if (!_bundler.compilers.containsKey(moduleName)) {
+      reportError('Cannot find kernel2js compiler for $moduleName.');
+      return;
+    }
 
-      var evaluator = new ExpressionCompiler(
-          _generator.generator, kernel2jsCompiler, _component,
-          verbose: _compilerOptions.verbose,
-          onDiagnostic: _compilerOptions.onDiagnostic);
+    final String boundaryKey = Uuid().generateV4();
+    _outputStream.writeln('result $boundaryKey');
 
-      var procedure = await evaluator.compileExpressionToJs(libraryUri, line,
-          column, jsModules, jsFrameValues, moduleName, expression);
+    var kernel2jsCompiler = _bundler.compilers[moduleName];
 
-      var result = errors.length > 0 ? errors[0] : procedure;
+    var evaluator = new ExpressionCompiler(
+        _generator.generator, kernel2jsCompiler, _component,
+        verbose: _compilerOptions.verbose,
+        onDiagnostic: _compilerOptions.onDiagnostic);
 
-      // TODO(annagrin): kernelBinaryFilename is too specific
-      // rename to _outputFileName?
-      await File(_kernelBinaryFilename).writeAsString(result);
+    var procedure = await evaluator.compileExpressionToJs(libraryUri, line,
+        column, jsModules, jsFrameValues, moduleName, expression);
 
-      _outputStream
-          .writeln('$boundaryKey $_kernelBinaryFilename ${errors.length}');
+    var result = errors.length > 0 ? errors[0] : procedure;
 
-      // TODO(annagrin): do we need to add asserts/error reporting if
-      // initial compilation didn't happen and _kernelBinaryFilename
-      // is different from below?
-      if (procedure != null) {
-        _kernelBinaryFilename = _kernelBinaryFilenameIncremental;
-      }
-    } else {
-      _outputStream.writeln('$boundaryKey');
+    // TODO(annagrin): kernelBinaryFilename is too specific
+    // rename to _outputFileName?
+    await File(_kernelBinaryFilename).writeAsString(result);
+
+    _outputStream
+        .writeln('$boundaryKey $_kernelBinaryFilename ${errors.length}');
+
+    // TODO(annagrin): do we need to add asserts/error reporting if
+    // initial compilation didn't happen and _kernelBinaryFilename
+    // is different from below?
+    if (procedure != null) {
+      _kernelBinaryFilename = _kernelBinaryFilenameIncremental;
     }
   }
 
diff --git a/pkg/kernel/lib/text/serializer_combinators.dart b/pkg/kernel/lib/text/serializer_combinators.dart
index e166cb2..45de839 100644
--- a/pkg/kernel/lib/text/serializer_combinators.dart
+++ b/pkg/kernel/lib/text/serializer_combinators.dart
@@ -185,6 +185,19 @@
   }
 }
 
+class UriSerializer extends TextSerializer<Uri> {
+  const UriSerializer();
+
+  Uri readFrom(Iterator<Object> stream, DeserializationState state) {
+    String uriAsString = const DartString().readFrom(stream, state);
+    return Uri.parse(uriAsString);
+  }
+
+  void writeTo(StringBuffer buffer, Uri object, SerializationState state) {
+    const DartString().writeTo(buffer, object.toString(), state);
+  }
+}
+
 // == Serializers for tagged (disjoint) unions.
 //
 // They require a function mapping serializables to a tag string.  This is
@@ -195,7 +208,7 @@
   final List<String> tags;
   final List<TextSerializer<T>> serializers;
 
-  Case(this.tagger, this.tags, this.serializers);
+  const Case(this.tagger, this.tags, this.serializers);
 
   Case.uninitialized(this.tagger)
       : tags = [],
@@ -249,7 +262,7 @@
   final K Function(S) wrap;
   final TextSerializer<S> contents;
 
-  Wrapped(this.unwrap, this.wrap, this.contents);
+  const Wrapped(this.unwrap, this.wrap, this.contents);
 
   K readFrom(Iterator<Object> stream, DeserializationState state) {
     return wrap(contents.readFrom(stream, state));
diff --git a/pkg/kernel/lib/text/text_serializer.dart b/pkg/kernel/lib/text/text_serializer.dart
index 467af4d..633e8c6 100644
--- a/pkg/kernel/lib/text/text_serializer.dart
+++ b/pkg/kernel/lib/text/text_serializer.dart
@@ -20,14 +20,14 @@
   String tag(Name name) => name.isPrivate ? "private" : "public";
 }
 
-TextSerializer<Name> publicName =
-    new Wrapped(unwrapPublicName, wrapPublicName, const DartString());
+const TextSerializer<Name> publicName =
+    const Wrapped(unwrapPublicName, wrapPublicName, const DartString());
 
 String unwrapPublicName(Name name) => name.name;
 
 Name wrapPublicName(String name) => new Name(name);
 
-TextSerializer<Name> privateName = new Wrapped(unwrapPrivateName,
+const TextSerializer<Name> privateName = const Wrapped(unwrapPrivateName,
     wrapPrivateName, Tuple2Serializer(const DartString(), const DartString()));
 
 Tuple2<String, String> unwrapPrivateName(Name name) {
@@ -40,7 +40,7 @@
   throw UnimplementedError('deserialization of private names');
 }
 
-TextSerializer<Name> nameSerializer = new Case(const NameTagger(), [
+const TextSerializer<Name> nameSerializer = const Case(const NameTagger(), [
   "public",
   "private",
 ], [
@@ -119,8 +119,9 @@
   String visitFunctionExpression(FunctionExpression _) => "fun";
 }
 
-TextSerializer<InvalidExpression> invalidExpressionSerializer = new Wrapped(
-    unwrapInvalidExpression, wrapInvalidExpression, const DartString());
+const TextSerializer<InvalidExpression> invalidExpressionSerializer =
+    const Wrapped(
+        unwrapInvalidExpression, wrapInvalidExpression, const DartString());
 
 String unwrapInvalidExpression(InvalidExpression expression) {
   return expression.message;
@@ -173,57 +174,57 @@
   return new StringConcatenation(expressions);
 }
 
-TextSerializer<StringLiteral> stringLiteralSerializer =
-    new Wrapped(unwrapStringLiteral, wrapStringLiteral, const DartString());
+const TextSerializer<StringLiteral> stringLiteralSerializer =
+    const Wrapped(unwrapStringLiteral, wrapStringLiteral, const DartString());
 
 String unwrapStringLiteral(StringLiteral literal) => literal.value;
 
 StringLiteral wrapStringLiteral(String value) => new StringLiteral(value);
 
-TextSerializer<IntLiteral> intLiteralSerializer =
-    new Wrapped(unwrapIntLiteral, wrapIntLiteral, const DartInt());
+const TextSerializer<IntLiteral> intLiteralSerializer =
+    const Wrapped(unwrapIntLiteral, wrapIntLiteral, const DartInt());
 
 int unwrapIntLiteral(IntLiteral literal) => literal.value;
 
 IntLiteral wrapIntLiteral(int value) => new IntLiteral(value);
 
-TextSerializer<DoubleLiteral> doubleLiteralSerializer =
-    new Wrapped(unwrapDoubleLiteral, wrapDoubleLiteral, const DartDouble());
+const TextSerializer<DoubleLiteral> doubleLiteralSerializer =
+    const Wrapped(unwrapDoubleLiteral, wrapDoubleLiteral, const DartDouble());
 
 double unwrapDoubleLiteral(DoubleLiteral literal) => literal.value;
 
 DoubleLiteral wrapDoubleLiteral(double value) => new DoubleLiteral(value);
 
-TextSerializer<BoolLiteral> boolLiteralSerializer =
-    new Wrapped(unwrapBoolLiteral, wrapBoolLiteral, const DartBool());
+const TextSerializer<BoolLiteral> boolLiteralSerializer =
+    const Wrapped(unwrapBoolLiteral, wrapBoolLiteral, const DartBool());
 
 bool unwrapBoolLiteral(BoolLiteral literal) => literal.value;
 
 BoolLiteral wrapBoolLiteral(bool value) => new BoolLiteral(value);
 
-TextSerializer<NullLiteral> nullLiteralSerializer =
-    new Wrapped(unwrapNullLiteral, wrapNullLiteral, const Nothing());
+const TextSerializer<NullLiteral> nullLiteralSerializer =
+    const Wrapped(unwrapNullLiteral, wrapNullLiteral, const Nothing());
 
 void unwrapNullLiteral(NullLiteral literal) {}
 
 NullLiteral wrapNullLiteral(void ignored) => new NullLiteral();
 
-TextSerializer<SymbolLiteral> symbolLiteralSerializer =
-    new Wrapped(unwrapSymbolLiteral, wrapSymbolLiteral, const DartString());
+const TextSerializer<SymbolLiteral> symbolLiteralSerializer =
+    const Wrapped(unwrapSymbolLiteral, wrapSymbolLiteral, const DartString());
 
 String unwrapSymbolLiteral(SymbolLiteral expression) => expression.value;
 
 SymbolLiteral wrapSymbolLiteral(String value) => new SymbolLiteral(value);
 
-TextSerializer<ThisExpression> thisExpressionSerializer =
-    new Wrapped(unwrapThisExpression, wrapThisExpression, const Nothing());
+const TextSerializer<ThisExpression> thisExpressionSerializer =
+    const Wrapped(unwrapThisExpression, wrapThisExpression, const Nothing());
 
 void unwrapThisExpression(ThisExpression expression) {}
 
 ThisExpression wrapThisExpression(void ignored) => new ThisExpression();
 
-TextSerializer<Rethrow> rethrowSerializer =
-    new Wrapped(unwrapRethrow, wrapRethrow, const Nothing());
+const TextSerializer<Rethrow> rethrowSerializer =
+    const Wrapped(unwrapRethrow, wrapRethrow, const Nothing());
 
 void unwrapRethrow(Rethrow expression) {}
 
@@ -439,8 +440,8 @@
   return new PropertySet(tuple.first, tuple.second, tuple.third);
 }
 
-TextSerializer<SuperPropertyGet> superPropertyGetSerializer =
-    new Wrapped(unwrapSuperPropertyGet, wrapSuperPropertyGet, nameSerializer);
+const TextSerializer<SuperPropertyGet> superPropertyGetSerializer =
+    const Wrapped(unwrapSuperPropertyGet, wrapSuperPropertyGet, nameSerializer);
 
 Name unwrapSuperPropertyGet(SuperPropertyGet expression) {
   return expression.name;
@@ -553,8 +554,8 @@
   }
 }
 
-TextSerializer<StaticGet> staticGetSerializer =
-    new Wrapped(unwrapStaticGet, wrapStaticGet, new CanonicalNameSerializer());
+const TextSerializer<StaticGet> staticGetSerializer = const Wrapped(
+    unwrapStaticGet, wrapStaticGet, const CanonicalNameSerializer());
 
 CanonicalName unwrapStaticGet(StaticGet expression) {
   return expression.targetReference.canonicalName;
@@ -831,8 +832,8 @@
   constDeclarationSerializer,
 ]);
 
-TextSerializer<TypeParameter> typeParameterSerializer =
-    new Wrapped(unwrapTypeParameter, wrapTypeParameter, const DartString());
+const TextSerializer<TypeParameter> typeParameterSerializer =
+    const Wrapped(unwrapTypeParameter, wrapTypeParameter, const DartString());
 
 String unwrapTypeParameter(TypeParameter node) => node.name;
 
@@ -889,29 +890,29 @@
   String visitTypeParameterType(TypeParameterType _) => "par";
 }
 
-TextSerializer<InvalidType> invalidTypeSerializer =
-    new Wrapped(unwrapInvalidType, wrapInvalidType, const Nothing());
+const TextSerializer<InvalidType> invalidTypeSerializer =
+    const Wrapped(unwrapInvalidType, wrapInvalidType, const Nothing());
 
 void unwrapInvalidType(InvalidType type) {}
 
 InvalidType wrapInvalidType(void ignored) => const InvalidType();
 
-TextSerializer<DynamicType> dynamicTypeSerializer =
-    new Wrapped(unwrapDynamicType, wrapDynamicType, const Nothing());
+const TextSerializer<DynamicType> dynamicTypeSerializer =
+    const Wrapped(unwrapDynamicType, wrapDynamicType, const Nothing());
 
 void unwrapDynamicType(DynamicType type) {}
 
 DynamicType wrapDynamicType(void ignored) => const DynamicType();
 
-TextSerializer<VoidType> voidTypeSerializer =
-    new Wrapped(unwrapVoidType, wrapVoidType, const Nothing());
+const TextSerializer<VoidType> voidTypeSerializer =
+    const Wrapped(unwrapVoidType, wrapVoidType, const Nothing());
 
 void unwrapVoidType(VoidType type) {}
 
 VoidType wrapVoidType(void ignored) => const VoidType();
 
-TextSerializer<BottomType> bottomTypeSerializer =
-    new Wrapped(unwrapBottomType, wrapBottomType, const Nothing());
+const TextSerializer<BottomType> bottomTypeSerializer =
+    const Wrapped(unwrapBottomType, wrapBottomType, const Nothing());
 
 void unwrapBottomType(BottomType type) {}
 
@@ -1258,6 +1259,31 @@
 Case<Procedure> procedureSerializer =
     new Case.uninitialized(const ProcedureTagger());
 
+class LibraryTagger implements Tagger<Library> {
+  const LibraryTagger();
+
+  String tag(Library node) {
+    return node.isNonNullableByDefault ? "null-safe" : "legacy";
+  }
+}
+
+TextSerializer<Library> libraryContentsSerializer = new Wrapped(
+  unwrapLibraryNode,
+  wrapLibraryNode,
+  new Tuple2Serializer(
+      const UriSerializer(), new ListSerializer(procedureSerializer)),
+);
+
+Tuple2<Uri, List<Procedure>> unwrapLibraryNode(Library library) {
+  return new Tuple2(library.importUri, library.procedures);
+}
+
+Library wrapLibraryNode(Tuple2<Uri, List<Procedure>> tuple) {
+  return new Library(tuple.first, procedures: tuple.second);
+}
+
+Case<Library> librarySerializer = new Case.uninitialized(const LibraryTagger());
+
 void initializeSerializers() {
   expressionSerializer.tags.addAll([
     "string",
@@ -1391,4 +1417,9 @@
   ]);
   procedureSerializer.tags.addAll(["static-method"]);
   procedureSerializer.serializers.addAll([staticMethodSerializer]);
+  librarySerializer.tags.addAll(["legacy", "null-safe"]);
+  librarySerializer.serializers.addAll([
+    libraryContentsSerializer,
+    libraryContentsSerializer,
+  ]);
 }
diff --git a/pkg/kernel/test/text_serializer_from_kernel_nodes_test.dart b/pkg/kernel/test/text_serializer_from_kernel_nodes_test.dart
index 2857826..eb2f032 100644
--- a/pkg/kernel/test/text_serializer_from_kernel_nodes_test.dart
+++ b/pkg/kernel/test/text_serializer_from_kernel_nodes_test.dart
@@ -13,87 +13,42 @@
   test();
 }
 
-abstract class TestCase<T extends Node> {
+class TestCase<T extends Node> {
   final String name;
   final T node;
   final SerializationState serializationState;
   final DeserializationState deserializationState;
   final String expectation;
+  final TextSerializer<T> serializer;
 
-  TestCase._(
+  TestCase(
       {this.name,
       this.node,
       this.expectation,
+      this.serializer,
       SerializationState serializationState,
       DeserializationState deserializationState})
-      : this.serializationState =
+      : assert(node != null),
+        assert(expectation != null),
+        assert(serializer != null),
+        this.serializationState =
             serializationState ?? new SerializationState(null),
         this.deserializationState = deserializationState ??
             new DeserializationState(null, new CanonicalName.root());
 
-  T readNode(String input, DeserializationState state);
-
-  String writeNode(T node, SerializationState state);
-}
-
-class StatementTestCase extends TestCase<Statement> {
-  StatementTestCase(
-      {String name,
-      Statement node,
-      String expectation,
-      SerializationState serializationState,
-      DeserializationState deserializationState})
-      : super._(
-            name: name,
-            node: node,
-            expectation: expectation,
-            serializationState: serializationState,
-            deserializationState: deserializationState);
-
-  Statement readNode(String input, DeserializationState state) {
+  T readNode(String input, DeserializationState state) {
     TextIterator stream = new TextIterator(input, 0);
     stream.moveNext();
-    Statement result = statementSerializer.readFrom(stream, state);
+    T result = serializer.readFrom(stream, state);
     if (stream.moveNext()) {
-      throw new StateError("Found extra tokens at the end of the statement.");
+      throw new StateError("Found extra tokens at the end.");
     }
     return result;
   }
 
-  String writeNode(Statement statement, SerializationState state) {
+  String writeNode(T node, SerializationState state) {
     StringBuffer buffer = new StringBuffer();
-    statementSerializer.writeTo(buffer, statement, state);
-    return buffer.toString();
-  }
-}
-
-class ProcedureTestCase extends TestCase<Procedure> {
-  ProcedureTestCase(
-      {String name,
-      Procedure node,
-      String expectation,
-      SerializationState serializationState,
-      DeserializationState deserializationState})
-      : super._(
-            name: name,
-            node: node,
-            expectation: expectation,
-            serializationState: serializationState,
-            deserializationState: deserializationState);
-
-  Procedure readNode(String input, DeserializationState state) {
-    TextIterator stream = new TextIterator(input, 0);
-    stream.moveNext();
-    Procedure result = procedureSerializer.readFrom(stream, state);
-    if (stream.moveNext()) {
-      throw new StateError("Found extra tokens at the end of the procedure.");
-    }
-    return result;
-  }
-
-  String writeNode(Procedure procedure, SerializationState state) {
-    StringBuffer buffer = new StringBuffer();
-    procedureSerializer.writeTo(buffer, procedure, state);
+    serializer.writeTo(buffer, node, state);
     return buffer.toString();
   }
 }
@@ -101,7 +56,7 @@
 void test() {
   List<String> failures = [];
   List<TestCase> tests = <TestCase>[
-    new StatementTestCase(
+    new TestCase<Statement>(
         name: 'let dynamic x = 42 in x;',
         node: () {
           VariableDeclaration x = new VariableDeclaration('x',
@@ -110,8 +65,9 @@
         }(),
         expectation: ''
             '(expr (let (var "x^0" (dynamic) (int 42) ())'
-            ' (get-var "x^0" _)))'),
-    new StatementTestCase(
+            ' (get-var "x^0" _)))',
+        serializer: statementSerializer),
+    new TestCase<Statement>(
         name: 'let dynamic x = 42 in let Bottom x^0 = null in x;',
         node: () {
           VariableDeclaration outterLetVar = new VariableDeclaration('x',
@@ -124,8 +80,9 @@
         expectation: ''
             '(expr (let (var "x^0" (dynamic) (int 42) ())'
             ' (let (var "x^1" (bottom) (null) ())'
-            ' (get-var "x^0" _))))'),
-    new StatementTestCase(
+            ' (get-var "x^0" _))))',
+        serializer: statementSerializer),
+    new TestCase<Statement>(
         name: 'let dynamic x = 42 in let Bottom x^0 = null in x^0;',
         node: () {
           VariableDeclaration outterLetVar = new VariableDeclaration('x',
@@ -138,11 +95,12 @@
         expectation: ''
             '(expr (let (var "x^0" (dynamic) (int 42) ())'
             ' (let (var "x^1" (bottom) (null) ())'
-            ' (get-var "x^1" _))))'),
+            ' (get-var "x^1" _))))',
+        serializer: statementSerializer),
     () {
       VariableDeclaration x =
           new VariableDeclaration('x', type: const DynamicType());
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose: dynamic x; */ x = 42;',
           node: new ExpressionStatement(new VariableSet(x, new IntLiteral(42))),
           expectation: '(expr (set-var "x^0" (int 42)))',
@@ -155,7 +113,8 @@
               new DeserializationEnvironment(null)
                 ..addBinder('x^0', x)
                 ..close(),
-              new CanonicalName.root()));
+              new CanonicalName.root()),
+          serializer: statementSerializer);
     }(),
     () {
       Field field = new Field(new Name('field'), type: const DynamicType());
@@ -164,13 +123,14 @@
           fields: <Field>[field]);
       Component component = new Component(libraries: <Library>[library]);
       component.computeCanonicalNames();
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose top-level: dynamic field; */ field;',
           node: new ExpressionStatement(new StaticGet(field)),
           expectation: ''
               '(expr (get-static "package:foo/bar.dart::@fields::field"))',
           serializationState: new SerializationState(null),
-          deserializationState: new DeserializationState(null, component.root));
+          deserializationState: new DeserializationState(null, component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Field field = new Field(new Name('field'), type: const DynamicType());
@@ -179,7 +139,7 @@
           fields: <Field>[field]);
       Component component = new Component(libraries: <Library>[library]);
       component.computeCanonicalNames();
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose top-level: dynamic field; */ field = 1;',
           node:
               new ExpressionStatement(new StaticSet(field, new IntLiteral(1))),
@@ -187,7 +147,8 @@
               '(expr'
               ' (set-static "package:foo/bar.dart::@fields::field" (int 1)))',
           serializationState: new SerializationState(null),
-          deserializationState: new DeserializationState(null, component.root));
+          deserializationState: new DeserializationState(null, component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Procedure topLevelProcedure = new Procedure(
@@ -202,7 +163,7 @@
           procedures: <Procedure>[topLevelProcedure]);
       Component component = new Component(libraries: <Library>[library]);
       component.computeCanonicalNames();
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose top-level: foo(dynamic x) {...}; */ foo(42);',
           node: new ExpressionStatement(new StaticInvocation.byReference(
               topLevelProcedure.reference,
@@ -212,7 +173,8 @@
               '(expr (invoke-static "package:foo/bar.dart::@methods::foo"'
               ' () ((int 42)) ()))',
           serializationState: new SerializationState(null),
-          deserializationState: new DeserializationState(null, component.root));
+          deserializationState: new DeserializationState(null, component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Procedure factoryConstructor = new Procedure(
@@ -225,7 +187,7 @@
           classes: <Class>[klass]);
       Component component = new Component(libraries: <Library>[library]);
       component.computeCanonicalNames();
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: ''
               '/* suppose A { const A(); const factory A.foo() = A; } */'
               ' const A.foo();',
@@ -237,7 +199,8 @@
               ' "package:foo/bar.dart::A::@factories::foo"'
               ' () () ()))',
           serializationState: new SerializationState(null),
-          deserializationState: new DeserializationState(null, component.root));
+          deserializationState: new DeserializationState(null, component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Field field = new Field(new Name('field'), type: const DynamicType());
@@ -250,7 +213,7 @@
 
       VariableDeclaration x =
           new VariableDeclaration('x', type: const DynamicType());
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose A {dynamic field;} A x; */ x.{A::field};',
           node: new ExpressionStatement(new DirectPropertyGet.byReference(
               new VariableGet(x), field.reference)),
@@ -265,7 +228,8 @@
               new DeserializationEnvironment(null)
                 ..addBinder('x^0', x)
                 ..close(),
-              component.root));
+              component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Field field = new Field(new Name('field'), type: const DynamicType());
@@ -278,7 +242,7 @@
 
       VariableDeclaration x =
           new VariableDeclaration('x', type: const DynamicType());
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose A {dynamic field;} A x; */ x.{A::field} = 42;',
           node: new ExpressionStatement(new DirectPropertySet.byReference(
               new VariableGet(x), field.reference, new IntLiteral(42))),
@@ -293,7 +257,8 @@
               new DeserializationEnvironment(null)
                 ..addBinder('x^0', x)
                 ..close(),
-              component.root));
+              component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Procedure method = new Procedure(
@@ -308,7 +273,7 @@
 
       VariableDeclaration x =
           new VariableDeclaration('x', type: const DynamicType());
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose A {foo() {...}} A x; */ x.{A::foo}();',
           node: new ExpressionStatement(new DirectMethodInvocation.byReference(
               new VariableGet(x), method.reference, new Arguments([]))),
@@ -324,7 +289,8 @@
               new DeserializationEnvironment(null)
                 ..addBinder('x^0', x)
                 ..close(),
-              component.root));
+              component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Constructor constructor =
@@ -336,7 +302,7 @@
           classes: <Class>[klass]);
       Component component = new Component(libraries: <Library>[library]);
       component.computeCanonicalNames();
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose A {A.foo();} */ new A();',
           node: new ExpressionStatement(new ConstructorInvocation.byReference(
               constructor.reference, new Arguments([]))),
@@ -345,7 +311,8 @@
               ' "package:foo/bar.dart::A::@constructors::foo"'
               ' () () ()))',
           serializationState: new SerializationState(null),
-          deserializationState: new DeserializationState(null, component.root));
+          deserializationState: new DeserializationState(null, component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Constructor constructor = new Constructor(new FunctionNode(null),
@@ -357,7 +324,7 @@
           classes: <Class>[klass]);
       Component component = new Component(libraries: <Library>[library]);
       component.computeCanonicalNames();
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose A {const A.foo();} */ const A();',
           node: new ExpressionStatement(new ConstructorInvocation.byReference(
               constructor.reference, new Arguments([]),
@@ -367,14 +334,15 @@
               ' "package:foo/bar.dart::A::@constructors::foo"'
               ' () () ()))',
           serializationState: new SerializationState(null),
-          deserializationState: new DeserializationState(null, component.root));
+          deserializationState: new DeserializationState(null, component.root),
+          serializer: statementSerializer);
     }(),
     () {
       TypeParameter outterParam =
           new TypeParameter('T', const DynamicType(), const DynamicType());
       TypeParameter innerParam =
           new TypeParameter('T', const DynamicType(), const DynamicType());
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* T Function<T>(T Function<T>()); */',
           node: new ExpressionStatement(new TypeLiteral(new FunctionType(
               [
@@ -394,7 +362,8 @@
           serializationState:
               new SerializationState(new SerializationEnvironment(null)),
           deserializationState: new DeserializationState(
-              new DeserializationEnvironment(null), null));
+              new DeserializationEnvironment(null), null),
+          serializer: statementSerializer);
     }(),
     () {
       TypeParameter t =
@@ -403,7 +372,7 @@
           type: new TypeParameterType(t, Nullability.legacy));
       VariableDeclaration t2 = new VariableDeclaration('t2',
           type: new TypeParameterType(t, Nullability.legacy));
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* <T>(T t1, [T t2]) => t1; */',
           node: new ExpressionStatement(new FunctionExpression(new FunctionNode(
               new ReturnStatement(new VariableGet(t1)),
@@ -420,7 +389,8 @@
           serializationState:
               new SerializationState(new SerializationEnvironment(null)),
           deserializationState: new DeserializationState(
-              new DeserializationEnvironment(null), null));
+              new DeserializationEnvironment(null), null),
+          serializer: statementSerializer);
     }(),
     () {
       VariableDeclaration x = VariableDeclaration('x', type: DynamicType());
@@ -434,16 +404,59 @@
           procedures: [foo]);
       Component component = Component(libraries: [library]);
       component.computeCanonicalNames();
-      return new ProcedureTestCase(
+      return new TestCase<Procedure>(
           name: 'foo(x) => x;',
           node: foo,
           expectation: ''
               '(static-method (public "foo")'
-              ' (sync () () () ((var "x^0" (dynamic) _ ())) () () (dynamic) (ret (get-var "x^0" _))))',
+              ' (sync () () () ((var "x^0" (dynamic) _ ())) () ()'
+              ' (dynamic) (ret (get-var "x^0" _))))',
           serializationState:
               new SerializationState(new SerializationEnvironment(null)),
           deserializationState: new DeserializationState(
-              new DeserializationEnvironment(null), null));
+              new DeserializationEnvironment(null), null),
+          serializer: procedureSerializer);
+    }(),
+    () {
+      VariableDeclaration x1 = VariableDeclaration('x', type: DynamicType());
+      VariableDeclaration x2 = VariableDeclaration('x', type: DynamicType());
+      Procedure foo = Procedure(
+          Name('foo'),
+          ProcedureKind.Method,
+          FunctionNode(ReturnStatement(VariableGet(x1)),
+              positionalParameters: [x1]),
+          isStatic: true);
+      Procedure bar = Procedure(
+          Name('bar'),
+          ProcedureKind.Method,
+          FunctionNode(
+              ReturnStatement(
+                  StaticInvocation(foo, Arguments([VariableGet(x2)]))),
+              positionalParameters: [x2]),
+          isStatic: true);
+      Library library = Library(Uri(scheme: 'package', path: 'foo/bar.dart'),
+          procedures: [foo, bar]);
+      Component component = Component(libraries: [library]);
+      component.computeCanonicalNames();
+      return new TestCase<Library>(
+          name: 'foo(x) => x; bar(x) => foo(x);',
+          node: library,
+          expectation: ''
+              '(legacy "package:foo/bar.dart"'
+              ''
+              ' ((static-method (public "foo")'
+              ' (sync () () () ((var "x^0" (dynamic) _ ())) () () (dynamic)'
+              ' (ret (get-var "x^0" _))))'
+              ''
+              ' (static-method (public "bar")'
+              ' (sync () () () ((var "x^0" (dynamic) _ ())) () () (dynamic)'
+              ' (ret (invoke-static "package:foo/bar.dart::@methods::foo"'
+              ' () ((get-var "x^0" _)) ()))))))',
+          serializationState:
+              new SerializationState(new SerializationEnvironment(null)),
+          deserializationState: new DeserializationState(
+              new DeserializationEnvironment(null), new CanonicalName.root()),
+          serializer: librarySerializer);
     }(),
   ];
   for (TestCase testCase in tests) {
@@ -451,8 +464,8 @@
         testCase.writeNode(testCase.node, testCase.serializationState);
     if (roundTripInput != testCase.expectation) {
       failures.add(''
-          '* initial serialization for test "${testCase.name}"'
-          ' gave output "${roundTripInput}"');
+          "* initial serialization for test '${testCase.name}'"
+          " gave output '${roundTripInput}'");
     }
 
     TreeNode deserialized =
@@ -461,7 +474,7 @@
         testCase.writeNode(deserialized, testCase.serializationState);
     if (roundTripOutput != roundTripInput) {
       failures.add(''
-          '* input "${testCase.name}" gave output "${roundTripOutput}"');
+          "* input '${testCase.name}' gave output '${roundTripOutput}'");
     }
   }
   if (failures.isNotEmpty) {
diff --git a/pkg/nnbd_migration/lib/nnbd_migration.dart b/pkg/nnbd_migration/lib/nnbd_migration.dart
index d32b605..c1c0d0f 100644
--- a/pkg/nnbd_migration/lib/nnbd_migration.dart
+++ b/pkg/nnbd_migration/lib/nnbd_migration.dart
@@ -13,6 +13,8 @@
 import 'package:nnbd_migration/instrumentation.dart';
 import 'package:nnbd_migration/src/nullability_migration_impl.dart';
 
+export 'package:nnbd_migration/src/utilities/hint_utils.dart' show HintComment;
+
 /// Description of fixes that might be performed by nullability migration.
 class NullabilityFixDescription {
   /// An if-test or conditional expression needs to have its condition and
@@ -24,6 +26,12 @@
     kind: NullabilityFixKind.removeDeadCode,
   );
 
+  /// A variable declaration needs to be marked as "late" due to the presence of
+  /// a `/*late*/` hint.
+  static const addLateDueToHint = const NullabilityFixDescription._(
+      appliedMessage: 'Added a late keyword, due to a hint',
+      kind: NullabilityFixKind.addLateDueToHint);
+
   /// An if-test or conditional expression needs to have its condition
   /// discarded.
   static const discardCondition = const NullabilityFixDescription._(
@@ -172,6 +180,7 @@
 
 /// An enumeration of the various kinds of nullability fixes.
 enum NullabilityFixKind {
+  addLateDueToHint,
   addRequired,
   checkExpression,
   checkExpressionDueToHint,
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index 6111ba3..8b82454 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -208,7 +208,7 @@
 
   final Set<PromotableElement> _lateHintedLocals = {};
 
-  Map<Token, NullabilityComment> _nullCheckHints = {};
+  Map<Token, HintComment> _nullCheckHints = {};
 
   EdgeBuilder(this.typeProvider, this._typeSystem, this._variables, this._graph,
       this.source, this.listener, this._decoratedClassHierarchy,
@@ -510,7 +510,7 @@
       _fieldsNotInitializedAtDeclaration = {
         for (var member in members)
           if (member is FieldDeclaration &&
-              !_variables.isLateHinted(source, member.fields))
+              _variables.getLateHint(source, member.fields) == null)
             for (var field in member.fields.variables)
               if (!field.declaredElement.isStatic && field.initializer == null)
                 field.declaredElement as FieldElement
@@ -1650,7 +1650,7 @@
       } else {
         assert(_flowAnalysis != null);
         if (declaredElement is PromotableElement &&
-            _variables.isLateHinted(source, node)) {
+            _variables.getLateHint(source, node) != null) {
           _lateHintedLocals.add(declaredElement);
         }
       }
@@ -1688,7 +1688,7 @@
           // when processing variable reads (only if flow analysis indicates
           // the variable isn't definitely assigned).
           if (isTopLevel &&
-              !_variables.isLateHinted(source, node) &&
+              _variables.getLateHint(source, node) == null &&
               !(declaredElement is FieldElement && !declaredElement.isStatic)) {
             _graph.makeNullable(
                 type.node, ImplicitNullInitializerOrigin(source, node));
@@ -2529,12 +2529,12 @@
       // Already visited this location.
       return type;
     }
-    switch (_nullCheckHints[token] = getPostfixHint(token)) {
-      case NullabilityComment.bang:
-        _variables.recordNullCheckHint(source, expression);
-        return type.withNode(_graph.never);
-      default:
-        return type;
+    var hint = _nullCheckHints[token] = getPostfixHint(token);
+    if (hint != null && hint.kind == HintCommentKind.bang) {
+      _variables.recordNullCheckHint(source, expression, hint);
+      return type.withNode(_graph.never);
+    } else {
+      return type;
     }
   }
 
diff --git a/pkg/nnbd_migration/lib/src/edit_plan.dart b/pkg/nnbd_migration/lib/src/edit_plan.dart
index 300dd76..25ccfef 100644
--- a/pkg/nnbd_migration/lib/src/edit_plan.dart
+++ b/pkg/nnbd_migration/lib/src/edit_plan.dart
@@ -14,6 +14,7 @@
 import 'package:nnbd_migration/fix_reason_target.dart';
 import 'package:nnbd_migration/instrumentation.dart';
 import 'package:nnbd_migration/nnbd_migration.dart';
+import 'package:nnbd_migration/src/utilities/hint_utils.dart';
 
 Map<int, List<AtomicEdit>> _removeCode(
     int offset, int end, _RemovalStyle removalStyle, AtomicEditInfo info) {
@@ -131,7 +132,11 @@
   /// The reasons for the edit.
   final Map<FixReasonTarget, FixReasonInfo> fixReasons;
 
-  AtomicEditInfo(this.description, this.fixReasons);
+  /// If the edit is being made due to a hint, the hint in question; otherwise
+  /// `null`.
+  final HintComment hintComment;
+
+  AtomicEditInfo(this.description, this.fixReasons, {this.hintComment});
 }
 
 /// An [EditPlan] is a builder capable of accumulating a set of edits to be
@@ -179,6 +184,36 @@
   EditPlanner(this.lineInfo, this.sourceText, {this.removeViaComments = false});
 
   /// Creates a new edit plan that consists of executing [innerPlan], and then
+  /// converting the late [hint] into an explicit `late`.
+  NodeProducingEditPlan acceptLateHint(
+      NodeProducingEditPlan innerPlan, HintComment hint,
+      {AtomicEditInfo info}) {
+    var affixPlan = innerPlan is _CommentAffixPlan
+        ? innerPlan
+        : _CommentAffixPlan(innerPlan);
+    var changes = hint.changesToAccept(sourceText, info: info);
+    assert(affixPlan.offset >= _endForChanges(changes));
+    affixPlan.offset = _offsetForChanges(changes);
+    affixPlan._prefixChanges = changes + affixPlan._prefixChanges;
+    return affixPlan;
+  }
+
+  /// Creates a new edit plan that consists of executing [innerPlan], and then
+  /// converting the nullability [hint] into an explicit `?` or `!`.
+  NodeProducingEditPlan acceptNullabilityOrNullCheckHint(
+      NodeProducingEditPlan innerPlan, HintComment hint,
+      {AtomicEditInfo info}) {
+    var affixPlan = innerPlan is _CommentAffixPlan
+        ? innerPlan
+        : _CommentAffixPlan(innerPlan);
+    var changes = hint.changesToAccept(sourceText);
+    assert(affixPlan.end <= _offsetForChanges(changes));
+    affixPlan.end = _endForChanges(changes);
+    affixPlan._postfixChanges += hint.changesToAccept(sourceText, info: info);
+    return affixPlan;
+  }
+
+  /// Creates a new edit plan that consists of executing [innerPlan], and then
   /// appending the given [operand], with an intervening binary [operator].
   ///
   /// Optional argument [info] contains information about why the change was
@@ -264,6 +299,21 @@
       _PassThroughBuilderImpl(node);
 
   /// Creates a new edit plan that consists of executing [innerPlan], and then
+  /// dropping the given nullability [hint].
+  NodeProducingEditPlan dropNullabilityHint(
+      NodeProducingEditPlan innerPlan, HintComment hint,
+      {AtomicEditInfo info}) {
+    var affixPlan = innerPlan is _CommentAffixPlan
+        ? innerPlan
+        : _CommentAffixPlan(innerPlan);
+    var changes = hint.changesToRemove(sourceText, info: info);
+    assert(affixPlan.end <= _offsetForChanges(changes));
+    affixPlan.end = _endForChanges(changes);
+    affixPlan._postfixChanges += changes;
+    return affixPlan;
+  }
+
+  /// Creates a new edit plan that consists of executing [innerPlan], and then
   /// appending an informative ` `, to illustrate that the type is non-nullable.
   ///
   /// Optional argument [info] contains information about why the change was
@@ -603,6 +653,18 @@
     return lineInfo.lineStarts[lineNumber - 1];
   }
 
+  int _endForChanges(Map<int, List<AtomicEdit>> changes) {
+    int result;
+    for (var entry in changes.entries) {
+      var end = entry.key;
+      for (var edit in entry.value) {
+        end += edit.length;
+      }
+      if (result == null || end > result) result = end;
+    }
+    return result;
+  }
+
   /// Finds the deepest entry in [builderStack] that matches an entry in
   /// [ancestryStack], taking advantage of the fact that [builderStack] walks
   /// stepwise down the AST, and [ancestryStack] walks stepwise up the AST, with
@@ -665,6 +727,14 @@
     return sourceText.substring(offset, end).trimRight().isEmpty;
   }
 
+  int _offsetForChanges(Map<int, List<AtomicEdit>> changes) {
+    int result;
+    for (var key in changes.keys) {
+      if (result == null || key < result) result = key;
+    }
+    return result;
+  }
+
   /// If the given [node] maintains a variable-length sequence of child nodes,
   /// returns a list containing those child nodes, otherwise returns `null`.
   ///
@@ -785,6 +855,28 @@
   NodeProducingEditPlan finish(EditPlanner planner);
 }
 
+/// [EditPlan] that wraps an inner plan with optional prefix and suffix changes.
+class _CommentAffixPlan extends _NestedEditPlan {
+  Map<int, List<AtomicEdit>> _prefixChanges;
+
+  Map<int, List<AtomicEdit>> _postfixChanges;
+
+  @override
+  int offset;
+
+  @override
+  int end;
+
+  _CommentAffixPlan(NodeProducingEditPlan innerPlan)
+      : offset = innerPlan.offset,
+        end = innerPlan.end,
+        super(innerPlan.sourceNode, innerPlan);
+
+  @override
+  Map<int, List<AtomicEdit>> _getChanges(bool parens) =>
+      _prefixChanges + innerPlan._getChanges(parens) + _postfixChanges;
+}
+
 /// Visitor that determines whether a given [AstNode] ends in a cascade.
 class _EndsInCascadeVisitor extends UnifyingAstVisitor<void> {
   bool endsInCascade = false;
diff --git a/pkg/nnbd_migration/lib/src/fix_aggregator.dart b/pkg/nnbd_migration/lib/src/fix_aggregator.dart
index bcf9379..da7b040 100644
--- a/pkg/nnbd_migration/lib/src/fix_aggregator.dart
+++ b/pkg/nnbd_migration/lib/src/fix_aggregator.dart
@@ -15,6 +15,7 @@
 import 'package:nnbd_migration/src/decorated_type.dart';
 import 'package:nnbd_migration/src/edit_plan.dart';
 import 'package:nnbd_migration/src/fix_builder.dart';
+import 'package:nnbd_migration/src/utilities/hint_utils.dart';
 
 /// Visitor that combines together the changes produced by [FixBuilder] into a
 /// concrete set of source code edits using the infrastructure of [EditPlan].
@@ -419,6 +420,8 @@
 
   AtomicEditInfo _addNullCheckInfo;
 
+  HintComment _addNullCheckHint;
+
   DartType _introducesAsType;
 
   AtomicEditInfo _introduceAsInfo;
@@ -439,10 +442,11 @@
   DartType get introducesAsType => _introducesAsType;
 
   /// Causes a null check to be added to this expression, with the given [info].
-  void addNullCheck(AtomicEditInfo info) {
+  void addNullCheck(AtomicEditInfo info, {HintComment hint}) {
     assert(!_addsNullCheck);
     _addsNullCheck = true;
     _addNullCheckInfo = info;
+    _addNullCheckHint = hint;
   }
 
   /// Causes a cast to the given [type] to be added to this expression, with
@@ -467,8 +471,14 @@
       FixAggregator aggregator, NodeProducingEditPlan innerPlan) {
     var plan = innerPlan;
     if (_addsNullCheck) {
-      plan = aggregator.planner
-          .addUnaryPostfix(plan, TokenType.BANG, info: _addNullCheckInfo);
+      var hint = _addNullCheckHint;
+      if (hint != null) {
+        plan = aggregator.planner.acceptNullabilityOrNullCheckHint(plan, hint,
+            info: _addNullCheckInfo);
+      } else {
+        plan = aggregator.planner
+            .addUnaryPostfix(plan, TokenType.BANG, info: _addNullCheckInfo);
+      }
     }
     if (_introducesAsType != null) {
       plan = aggregator.planner.addBinaryPostfix(
@@ -599,7 +609,7 @@
 class NodeChangeForTypeAnnotation extends NodeChange<TypeAnnotation> {
   bool _makeNullable = false;
 
-  bool _nullabilityDueToHint = false;
+  HintComment _nullabilityHint;
 
   /// The decorated type of the type annotation, or `null` if there is no
   /// decorated type info of interest.  If [makeNullable] is `true`, the node
@@ -616,14 +626,15 @@
   /// Indicates whether the type should be made nullable by adding a `?`.
   bool get makeNullable => _makeNullable;
 
-  /// Indicates whether we are making the type nullable due to a hint.
-  bool get makeNullableDueToHint => _nullabilityDueToHint;
+  /// If we are making the type nullable due to a hint, the comment that caused
+  /// it.
+  HintComment get nullabilityHint => _nullabilityHint;
 
   void recordNullability(DecoratedType decoratedType, bool makeNullable,
-      {bool nullabilityDueToHint: false}) {
+      {HintComment nullabilityHint}) {
     _decoratedType = decoratedType;
     _makeNullable = makeNullable;
-    _nullabilityDueToHint = nullabilityDueToHint;
+    _nullabilityHint = nullabilityHint;
   }
 
   @override
@@ -633,25 +644,35 @@
     var typeName = _decoratedType.type.getDisplayString(withNullability: false);
     var fixReasons = {FixReasonTarget.root: _decoratedType.node};
     if (_makeNullable) {
-      NullabilityFixDescription description;
-      if (_nullabilityDueToHint) {
-        description =
-            NullabilityFixDescription.makeTypeNullableDueToHint(typeName);
+      var hint = _nullabilityHint;
+      if (hint != null) {
+        return aggregator.planner.acceptNullabilityOrNullCheckHint(
+            innerPlan, hint,
+            info: AtomicEditInfo(
+                NullabilityFixDescription.makeTypeNullableDueToHint(typeName),
+                fixReasons,
+                hintComment: hint));
       } else {
-        description = NullabilityFixDescription.makeTypeNullable(typeName);
+        return aggregator.planner.makeNullable(innerPlan,
+            info: AtomicEditInfo(
+                NullabilityFixDescription.makeTypeNullable(typeName),
+                fixReasons));
       }
-      return aggregator.planner.makeNullable(innerPlan,
-          info: AtomicEditInfo(description, fixReasons));
     } else {
-      NullabilityFixDescription description;
-      if (_nullabilityDueToHint) {
-        description =
-            NullabilityFixDescription.typeNotMadeNullableDueToHint(typeName);
+      var hint = _nullabilityHint;
+      if (hint != null) {
+        return aggregator.planner.dropNullabilityHint(innerPlan, hint,
+            info: AtomicEditInfo(
+                NullabilityFixDescription.typeNotMadeNullableDueToHint(
+                    typeName),
+                fixReasons,
+                hintComment: hint));
       } else {
-        description = NullabilityFixDescription.typeNotMadeNullable(typeName);
+        return aggregator.planner.explainNonNullable(innerPlan,
+            info: AtomicEditInfo(
+                NullabilityFixDescription.typeNotMadeNullable(typeName),
+                fixReasons));
       }
-      return aggregator.planner.explainNonNullable(innerPlan,
-          info: AtomicEditInfo(description, fixReasons));
     }
   }
 }
@@ -664,21 +685,15 @@
   /// that should be added.  Otherwise `null`.
   DartType addExplicitType;
 
-  /// Indicates whether a "late" annotation should be added to this variable
-  /// declaration.
-  bool addLate = false;
+  /// If a "late" annotation should be added to this variable  declaration, the
+  /// hint that caused it.  Otherwise `null`.
+  HintComment lateHint;
 
   NodeChangeForVariableDeclarationList() : super._();
 
   @override
   EditPlan _apply(VariableDeclarationList node, FixAggregator aggregator) {
     List<EditPlan> innerPlans = [];
-    if (addLate) {
-      innerPlans.add(aggregator.planner.insertText(
-          node,
-          node.firstTokenAfterCommentAndMetadata.offset,
-          [AtomicEdit.insert('late'), AtomicEdit.insert(' ')]));
-    }
     if (addExplicitType != null) {
       var typeText = addExplicitType.getDisplayString(withNullability: true);
       if (node.keyword?.keyword == Keyword.VAR) {
@@ -696,7 +711,13 @@
       }
     }
     innerPlans.addAll(aggregator.innerPlansForNode(node));
-    return aggregator.planner.passThrough(node, innerPlans: innerPlans);
+    var plan = aggregator.planner.passThrough(node, innerPlans: innerPlans);
+    if (lateHint != null) {
+      plan = aggregator.planner.acceptLateHint(plan, lateHint,
+          info: AtomicEditInfo(NullabilityFixDescription.addLateDueToHint, {},
+              hintComment: lateHint));
+    }
+    return plan;
   }
 }
 
diff --git a/pkg/nnbd_migration/lib/src/fix_builder.dart b/pkg/nnbd_migration/lib/src/fix_builder.dart
index 087a8ae..35177f7 100644
--- a/pkg/nnbd_migration/lib/src/fix_builder.dart
+++ b/pkg/nnbd_migration/lib/src/fix_builder.dart
@@ -33,6 +33,7 @@
 import 'package:nnbd_migration/src/edit_plan.dart';
 import 'package:nnbd_migration/src/fix_aggregator.dart';
 import 'package:nnbd_migration/src/nullability_node.dart';
+import 'package:nnbd_migration/src/utilities/hint_utils.dart';
 import 'package:nnbd_migration/src/utilities/permissive_mode.dart';
 import 'package:nnbd_migration/src/utilities/resolution_utils.dart';
 import 'package:nnbd_migration/src/variables.dart';
@@ -398,13 +399,18 @@
   @override
   DartType modifyExpressionType(Expression node, DartType type) =>
       _wrapExceptions(node, () => type, () {
-        if (_fixBuilder._variables.hasNullCheckHint(_fixBuilder.source, node)) {
+        var hint =
+            _fixBuilder._variables.getNullCheckHint(_fixBuilder.source, node);
+        if (hint != null) {
           type = _addNullCheck(node, type,
               info: AtomicEditInfo(
-                  NullabilityFixDescription.checkExpressionDueToHint, {
-                FixReasonTarget.root:
-                    FixReason_NullCheckHint(CodeReference.fromAstNode(node))
-              }));
+                  NullabilityFixDescription.checkExpressionDueToHint,
+                  {
+                    FixReasonTarget.root:
+                        FixReason_NullCheckHint(CodeReference.fromAstNode(node))
+                  },
+                  hintComment: hint),
+              hint: hint);
         }
         if (type.isDynamic) return type;
         var ancestor = _findNullabilityContextAncestor(node);
@@ -464,7 +470,7 @@
   }
 
   DartType _addNullCheck(Expression node, DartType type,
-      {AtomicEditInfo info}) {
+      {AtomicEditInfo info, HintComment hint}) {
     var checks =
         _fixBuilder._variables.expressionChecks(_fixBuilder.source, node);
     info ??= checks != null
@@ -472,7 +478,7 @@
             NullabilityFixDescription.checkExpression, checks.edges)
         : null;
     (_fixBuilder._getChange(node) as NodeChangeForExpression)
-        .addNullCheck(info);
+        .addNullCheck(info, hint: hint);
     _flowAnalysis.nonNullAssert_end(node);
     return _fixBuilder._typeSystem.promoteToNonNull(type as TypeImpl);
   }
@@ -654,9 +660,10 @@
         }
       }
     }
-    if (_fixBuilder._variables.isLateHinted(source, node)) {
+    var lateHint = _fixBuilder._variables.getLateHint(source, node);
+    if (lateHint != null) {
       (_fixBuilder._getChange(node) as NodeChangeForVariableDeclarationList)
-          .addLate = true;
+          .lateHint = lateHint;
     }
     super.visitVariableDeclarationList(node);
   }
@@ -755,7 +762,7 @@
     (_fixBuilder._getChange(node) as NodeChangeForTypeAnnotation)
         .recordNullability(
             decoratedType, decoratedType.node.isNullable,
-            nullabilityDueToHint:
-                _fixBuilder._variables.hasNullabilityHint(source, node));
+            nullabilityHint:
+                _fixBuilder._variables.getNullabilityHint(source, node));
   }
 }
diff --git a/pkg/nnbd_migration/lib/src/node_builder.dart b/pkg/nnbd_migration/lib/src/node_builder.dart
index d5a1de0..4b19615 100644
--- a/pkg/nnbd_migration/lib/src/node_builder.dart
+++ b/pkg/nnbd_migration/lib/src/node_builder.dart
@@ -493,19 +493,22 @@
           namedParameters: namedParameters);
     }
     _variables.recordDecoratedTypeAnnotation(source, node, decoratedType);
-    switch (getPostfixHint(node.endToken)) {
-      case NullabilityComment.bang:
-        _graph.makeNonNullableUnion(
-            decoratedType.node, NullabilityCommentOrigin(source, node, false));
-        _variables.recordNullabilityHint(source, node);
-        break;
-      case NullabilityComment.question:
-        _graph.makeNullableUnion(
-            decoratedType.node, NullabilityCommentOrigin(source, node, true));
-        _variables.recordNullabilityHint(source, node);
-        break;
-      case NullabilityComment.none:
-        break;
+    var hint = getPostfixHint(node.endToken);
+    if (hint != null) {
+      switch (hint.kind) {
+        case HintCommentKind.bang:
+          _graph.makeNonNullableUnion(decoratedType.node,
+              NullabilityCommentOrigin(source, node, false));
+          _variables.recordNullabilityHint(source, node, hint);
+          break;
+        case HintCommentKind.question:
+          _graph.makeNullableUnion(
+              decoratedType.node, NullabilityCommentOrigin(source, node, true));
+          _variables.recordNullabilityHint(source, node, hint);
+          break;
+        default:
+          break;
+      }
     }
     return decoratedType;
   }
@@ -539,12 +542,9 @@
     node.metadata.accept(this);
     var typeAnnotation = node.type;
     var type = typeAnnotation?.accept(this);
-    switch (getPrefixHint(node.firstTokenAfterCommentAndMetadata)) {
-      case PrefixHintComment.late_:
-        _variables.recordLateHint(source, node);
-        break;
-      case PrefixHintComment.none:
-        break;
+    var hint = getPrefixHint(node.firstTokenAfterCommentAndMetadata);
+    if (hint != null && hint.kind == HintCommentKind.late_) {
+      _variables.recordLateHint(source, node, hint);
     }
     for (var variable in node.variables) {
       variable.metadata.accept(this);
diff --git a/pkg/nnbd_migration/lib/src/utilities/hint_utils.dart b/pkg/nnbd_migration/lib/src/utilities/hint_utils.dart
index d0fa9c8..131fabf 100644
--- a/pkg/nnbd_migration/lib/src/utilities/hint_utils.dart
+++ b/pkg/nnbd_migration/lib/src/utilities/hint_utils.dart
@@ -3,26 +3,36 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:_fe_analyzer_shared/src/scanner/token.dart';
+import 'package:nnbd_migration/src/edit_plan.dart';
 
-/// Determine if the given token is a nullability hint, and if so, return the
-/// type of nullability hint it is.
-NullabilityComment classifyComment(Token token) {
-  if (token is CommentToken) {
-    if (token.lexeme == '/*!*/') return NullabilityComment.bang;
-    if (token.lexeme == '/*?*/') return NullabilityComment.question;
-  }
-  return NullabilityComment.none;
-}
-
-/// Determine if the given [token] is followed by a nullability hint, and if so,
-/// return the type of nullability hint it is followed by.
-NullabilityComment getPostfixHint(Token token) {
+/// Determines if the given [token] is followed by a nullability hint, and if
+/// so, returns information about it.  Otherwise returns `null`.
+HintComment getPostfixHint(Token token) {
   var commentToken = token.next.precedingComments;
-  var commentType = classifyComment(commentToken);
-  return commentType;
+  if (commentToken != null) {
+    HintCommentKind kind;
+    if (commentToken.lexeme == '/*!*/') {
+      kind = HintCommentKind.bang;
+    } else if (commentToken.lexeme == '/*?*/') {
+      kind = HintCommentKind.question;
+    } else {
+      return null;
+    }
+    return HintComment(
+        kind,
+        token.end,
+        commentToken.offset,
+        commentToken.offset + '/*'.length,
+        commentToken.end - '*/'.length,
+        commentToken.end,
+        commentToken.end);
+  }
+  return null;
 }
 
-PrefixHintComment getPrefixHint(Token token) {
+/// Determines if the given [token] is preceded by a hint, and if so, returns
+/// information about it.  Otherwise returns `null`.
+HintComment getPrefixHint(Token token) {
   Token commentToken = token.precedingComments;
   if (commentToken != null) {
     while (true) {
@@ -31,16 +41,150 @@
       commentToken = nextComment;
     }
     var lexeme = commentToken.lexeme;
-    if (lexeme.startsWith('/*') && lexeme.endsWith('*/') && lexeme.length > 4) {
-      var commentText = lexeme.substring(2, lexeme.length - 2).trim();
-      if (commentText == 'late') return PrefixHintComment.late_;
+    if (lexeme.startsWith('/*') &&
+        lexeme.endsWith('*/') &&
+        lexeme.length > 'late'.length) {
+      var commentText =
+          lexeme.substring('/*'.length, lexeme.length - '*/'.length).trim();
+      if (commentText == 'late') {
+        var commentOffset = commentToken.offset;
+        var lateOffset = commentOffset + commentToken.lexeme.indexOf('late');
+        return HintComment(
+            HintCommentKind.late_,
+            commentOffset,
+            commentOffset,
+            lateOffset,
+            lateOffset + 'late'.length,
+            commentToken.end,
+            token.offset);
+      }
     }
   }
-  return PrefixHintComment.none;
+  return null;
 }
 
-/// Types of comments that can influence nullability
-enum NullabilityComment {
+/// Information about a hint found in a source file.
+class HintComment {
+  static final _alphaNumericRegexp = RegExp('[a-zA-Z0-9]');
+
+  /// What kind of hint this is.
+  final HintCommentKind kind;
+
+  /// The file offset of the first character that should be removed if the hint
+  /// is to be removed.
+  final int _removeOffset;
+
+  /// The file offset of the first character of the hint comment itself.
+  final int _commentOffset;
+
+  /// The file offset of the first character that should be kept if the hint is
+  /// to be replaced with the hinted text.
+  final int _keepOffset;
+
+  /// The file offset just beyond the last character that should be kept if the
+  /// hint is to be replaced with the hinted text.
+  final int _keepEnd;
+
+  /// The file offset just beyond the last character of the hint comment itself.
+  final int _commentEnd;
+
+  /// The file offset just beyond the last character that should be removed if
+  /// the hint is to be removed.
+  final int _removeEnd;
+
+  HintComment(this.kind, this._removeOffset, this._commentOffset,
+      this._keepOffset, this._keepEnd, this._commentEnd, this._removeEnd)
+      : assert(_removeOffset <= _commentOffset),
+        assert(_commentOffset < _keepOffset),
+        assert(_keepOffset < _keepEnd),
+        assert(_keepEnd < _commentEnd),
+        assert(_commentEnd <= _removeEnd);
+
+  /// Creates the changes necessary to accept the given hint (replace it with
+  /// its contents and fix up whitespace).
+  Map<int, List<AtomicEdit>> changesToAccept(String sourceText,
+      {AtomicEditInfo info}) {
+    bool prependSpace = false;
+    bool appendSpace = false;
+    var removeOffset = _removeOffset;
+    var removeEnd = _removeEnd;
+    if (_isAlphaNumericBeforeOffset(sourceText, removeOffset) &&
+        _isAlphaNumericAtOffset(sourceText, _keepOffset)) {
+      if (sourceText[removeOffset] == ' ') {
+        // We can just keep this space.
+        removeOffset++;
+      } else {
+        prependSpace = true;
+      }
+    }
+    if (_isAlphaNumericBeforeOffset(sourceText, _keepEnd) &&
+        _isAlphaNumericAtOffset(sourceText, removeEnd)) {
+      if (sourceText[removeEnd - 1] == ' ') {
+        // We can just keep this space.
+        removeEnd--;
+      } else {
+        appendSpace = true;
+      }
+    }
+
+    return {
+      removeOffset: [
+        if (prependSpace) AtomicEdit.insert(' '),
+        AtomicEdit.delete(_keepOffset - removeOffset, info: info)
+      ],
+      _keepEnd: [AtomicEdit.delete(removeEnd - _keepEnd, info: info)],
+      if (appendSpace) removeEnd: [AtomicEdit.insert(' ')]
+    };
+  }
+
+  /// Creates the changes necessary to remove the given hint (and fix up
+  /// whitespace).
+  Map<int, List<AtomicEdit>> changesToRemove(String sourceText,
+      {AtomicEditInfo info}) {
+    bool appendSpace = false;
+    var removeOffset = this._removeOffset;
+    if (_isAlphaNumericBeforeOffset(sourceText, removeOffset) &&
+        _isAlphaNumericAtOffset(sourceText, _removeEnd)) {
+      if (sourceText[removeOffset] == ' ') {
+        // We can just keep this space.
+        removeOffset++;
+      } else {
+        appendSpace = true;
+      }
+    }
+    return {
+      removeOffset: [
+        AtomicEdit.delete(_removeEnd - removeOffset, info: info),
+        if (appendSpace) AtomicEdit.insert(' ')
+      ]
+    };
+  }
+
+  /// Creates the changes necessary to replace the given hint with a different
+  /// hint.
+  Map<int, List<AtomicEdit>> changesToReplace(
+      String sourceText, String replacement,
+      {AtomicEditInfo info}) {
+    return {
+      _commentOffset: [
+        AtomicEdit.replace(_commentEnd - _commentOffset, replacement,
+            info: info)
+      ]
+    };
+  }
+
+  static bool _isAlphaNumericAtOffset(String sourceText, int offset) {
+    return offset < sourceText.length &&
+        _alphaNumericRegexp.hasMatch(sourceText[offset]);
+  }
+
+  static bool _isAlphaNumericBeforeOffset(String sourceText, int offset) {
+    return offset > 0 && _alphaNumericRegexp.hasMatch(sourceText[offset - 1]);
+  }
+}
+
+/// Types of hint comments
+enum HintCommentKind {
   /// The comment `/*!*/`, which indicates that the type should not have a `?`
   /// appended.
   bang,
@@ -49,16 +193,7 @@
   /// appended.
   question,
 
-  /// No special comment.
-  none,
-}
-
-/// Types of comments that can appear before a token
-enum PrefixHintComment {
   /// The comment `/*late*/`, which indicates that the variable declaration
   /// should be late.
   late_,
-
-  /// No special comment
-  none,
 }
diff --git a/pkg/nnbd_migration/lib/src/variables.dart b/pkg/nnbd_migration/lib/src/variables.dart
index 6b751af..1d60f68 100644
--- a/pkg/nnbd_migration/lib/src/variables.dart
+++ b/pkg/nnbd_migration/lib/src/variables.dart
@@ -26,6 +26,7 @@
 import 'package:nnbd_migration/src/nullability_node.dart';
 import 'package:nnbd_migration/src/nullability_node_target.dart';
 import 'package:nnbd_migration/src/postmortem_file.dart';
+import 'package:nnbd_migration/src/utilities/hint_utils.dart';
 
 /// Data structure used by [Variables.spanForUniqueIdentifier] to return an
 /// offset/end pair.
@@ -56,11 +57,11 @@
 
   final _expressionChecks = <Source, Map<int, ExpressionChecks>>{};
 
-  final _lateHints = <Source, Set<int>>{};
+  final _lateHints = <Source, Map<int, HintComment>>{};
 
-  final _nullCheckHints = <Source, Set<int>>{};
+  final _nullCheckHints = <Source, Map<int, HintComment>>{};
 
-  final _nullabilityHints = <Source, Set<int>>{};
+  final _nullabilityHints = <Source, Map<int, HintComment>>{};
 
   final _unnecessaryCasts = <Source, Set<int>>{};
 
@@ -166,24 +167,26 @@
   ConditionalDiscard getConditionalDiscard(Source source, AstNode node) =>
       (_conditionalDiscards[source] ?? {})[node.offset];
 
-  /// Queries whether the given [node] is preceded by a `/*?*/` hint.  See
+  /// If the given [node] is preceded by a `/*late*/` hint, returns the
+  /// HintComment for it; otherwise returns `null`.  See [recordLateHint].
+  HintComment getLateHint(Source source, VariableDeclarationList node) {
+    return (_lateHints[source] ?? {})[node.offset];
+  }
+
+  /// If the given [node] is followed by a `/*?*/` or /*!*/ hint, returns the
+  /// HintComment for it; otherwise returns `null`.  See
   /// [recordNullabilityHint].
-  bool hasNullabilityHint(Source source, TypeAnnotation node) {
-    return (_nullabilityHints[source] ?? {})
-        .contains(uniqueIdentifierForSpan(node.offset, node.end));
+  HintComment getNullabilityHint(Source source, TypeAnnotation node) {
+    return (_nullabilityHints[source] ??
+        {})[uniqueIdentifierForSpan(node.offset, node.end)];
   }
 
-  /// Queries whether the given [expression] is followed by a null check hint
-  /// (`/*!*/`).  See [recordNullCheckHint].
-  bool hasNullCheckHint(Source source, Expression expression) {
-    return (_nullCheckHints[source] ?? {})
-        .contains(uniqueIdentifierForSpan(expression.offset, expression.end));
-  }
-
-  /// Queries whether the given [node] is preceded by a `/*late*/` hint.  See
-  /// [recordLateHint].
-  bool isLateHinted(Source source, VariableDeclarationList node) {
-    return (_lateHints[source] ?? {}).contains(node.offset);
+  /// If the given [expression] is followed by a null check hint (`/*!*/`),
+  /// returns the HintComment for it; otherwise returns `null`.  See
+  /// [recordNullCheckHint].
+  HintComment getNullCheckHint(Source source, Expression expression) {
+    return (_nullCheckHints[source] ??
+        {})[(uniqueIdentifierForSpan(expression.offset, expression.end))];
   }
 
   /// Records conditional discard information for the given AST node (which is
@@ -238,21 +241,25 @@
   }
 
   /// Records that the given [node] was preceded by a `/*late*/` hint.
-  void recordLateHint(Source source, VariableDeclarationList node) {
-    (_lateHints[source] ??= {}).add(node.offset);
+  void recordLateHint(
+      Source source, VariableDeclarationList node, HintComment hint) {
+    (_lateHints[source] ??= {})[node.offset] = hint;
   }
 
   /// Records that the given [node] was followed by a `/*?*/` or `/*!*/` hint.
-  void recordNullabilityHint(Source source, TypeAnnotation node) {
-    (_nullabilityHints[source] ??= {})
-        .add(uniqueIdentifierForSpan(node.offset, node.end));
+  void recordNullabilityHint(
+      Source source, TypeAnnotation node, HintComment hintComment) {
+    (_nullabilityHints[source] ??=
+        {})[uniqueIdentifierForSpan(node.offset, node.end)] = hintComment;
   }
 
   /// Records that the given [expression] is followed by a null check hint
   /// (`/*!*/`), for later recall by [hasNullCheckHint].
-  void recordNullCheckHint(Source source, Expression expression) {
-    (_nullCheckHints[source] ??= {})
-        .add(uniqueIdentifierForSpan(expression.offset, expression.end));
+  void recordNullCheckHint(
+      Source source, Expression expression, HintComment hintComment) {
+    (_nullCheckHints[source] ??=
+            {})[uniqueIdentifierForSpan(expression.offset, expression.end)] =
+        hintComment;
   }
 
   /// Records the fact that prior to migration, an unnecessary cast existed at
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index b69cf77..e4d7a54 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -224,7 +224,7 @@
 }
 ''';
     var expected = '''
-class C<T extends num?/*?*/> {}
+class C<T extends num?> {}
 
 void main() {
   C<num> c = C();
@@ -306,7 +306,7 @@
 }
 ''';
     var expected = '''
-class B<E> implements List<E?/*?*/> {
+class B<E> implements List<E?> {
   final C c;
   B(this.c);
   B<T> cast<T>() => c._castFrom<E, T>(this);
@@ -447,7 +447,7 @@
 }
 ''';
     var expected = '''
-void f(int/*!*/ i) {}
+void f(int i) {}
 void g(bool b, int? i) {
   if (b) f(i!);
 }
@@ -466,7 +466,7 @@
 ''';
     var expected = '''
 void _f() {
-  int?/*?*/ i = 0;
+  int? i = 0;
 }
 ''';
     await _checkSingleFileChanges(content, expected);
@@ -609,8 +609,8 @@
 num f4<T extends num>(bool b, num x, T y) => b ? x : y;
 
 void main() {
-  int? x1 = f1<int?/*?*/>(true, 0, null) as int?;
-  int? x2 = f2<int/*!*/>(true, 0, null) as int?;
+  int? x1 = f1<int?>(true, 0, null) as int?;
+  int? x2 = f2<int>(true, 0, null) as int?;
   int? x3 = f3<int>(true, null, 0) as int?;
   int x4 = f4<int>(true, 0, 0) as int;
 }
@@ -798,7 +798,7 @@
 ''';
     var expected = '''
 class C {
-  int?/*?*/ f = 0;
+  int? f = 0;
 }
 int? f(C c) => c.f;
 ''';
@@ -928,7 +928,7 @@
 abstract class C<T> {
   T getValue();
 }
-int? f(C<int?/*?*/> x) => x.getValue();
+int? f(C<int?> x) => x.getValue();
 ''';
     await _checkSingleFileChanges(content, expected);
   }
@@ -1245,7 +1245,7 @@
     // Note: https://github.com/dart-lang/sdk/issues/40471 tracks the fact that
     // we ought to alert the user to the presence of such casts.
     var expected = '''
-void f(int/*!*/ Function(int?) callback) {
+void f(int Function(int?) callback) {
   callback(null);
 }
 int? g(int? x) => x;
@@ -1332,16 +1332,14 @@
   nullNonNull = hardToNullNonNull
 }
 ''';
-
-    // TODO(paulberry): remove the /*!*/, /*?*/ comments on migration.
     var expected = '''
 void f(dynamic a) {
   List<int> hardToNonNullNonNull = a;
   List<int?> hardToNullNonNull = a;
   List<int>? hardToNonNullNull = a;
-  List<int/*!*/>/*!*/ nonNullNonNull;
-  List<int/*?*/>/*!*/ nullNonNull;
-  List<int/*!*/>/*?*/ nonNullNull;
+  List<int> nonNullNonNull;
+  List<int?> nullNonNull;
+  List<int>? nonNullNull;
   nonNullNonNull = hardToNonNullNonNull
   nonNullNull = hardToNonNullNull
   nullNonNull = hardToNullNonNull
@@ -1375,10 +1373,8 @@
     var content = '''
 List<int> f(Iterable<int/*?*/> a) => a;
 ''';
-
-    // TODO(paulberry): remove the /*!*/, /*?*/ comments on migration.
     var expected = '''
-List<int?> f(Iterable<int/*?*/> a) => a;
+List<int?> f(Iterable<int?> a) => a;
 ''';
     await _checkSingleFileChanges(content, expected);
   }
@@ -1406,8 +1402,6 @@
 abstract class C<A, B> implements List<A> {}
 C<int, num> f(List<int> a) => a;
 ''';
-
-    // TODO(paulberry): remove the /*!*/, /*?*/ comments on migration.
     var expected = '''
 abstract class C<A, B> implements List<A> {}
 C<int, num?> f(List<int> a) => a;
@@ -1509,7 +1503,7 @@
 List<int> _f(List<int/*!*/> xs) => [for(var x in xs) if (x == null) 1];
 ''';
     var expected = '''
-List<int> _f(List<int/*!*/> xs) => [];
+List<int> _f(List<int> xs) => [];
 ''';
     await _checkSingleFileChanges(content, expected);
   }
@@ -1692,7 +1686,7 @@
 int f(int/*?*/ i) => i + 1;
 ''';
     var expected = '''
-int f(int?/*?*/ i) => i! + 1;
+int f(int? i) => i! + 1;
 ''';
     await _checkSingleFileChanges(content, expected);
   }
@@ -1702,7 +1696,7 @@
 int f(int/*?*/ i) => i/*!*/;
 ''';
     var expected = '''
-int f(int?/*?*/ i) => i!/*!*/;
+int f(int? i) => i!;
 ''';
     await _checkSingleFileChanges(content, expected, removeViaComments: true);
   }
@@ -1714,7 +1708,7 @@
     // The user requested a null check so we should add it even if it's not
     // required to avoid compile errors.
     var expected = '''
-int?/*?*/ f(int?/*?*/ i) => i!/*!*/;
+int? f(int? i) => i!;
 ''';
     await _checkSingleFileChanges(content, expected, removeViaComments: true);
   }
@@ -1723,15 +1717,13 @@
     var content = 'int/*?*/ f() => 1/*!*/;';
     // The user requested a null check so we should add it even if it's not
     // required to avoid compile errors.
-    var expected = 'int?/*?*/ f() => 1!/*!*/;';
+    var expected = 'int? f() => 1!;';
     await _checkSingleFileChanges(content, expected, removeViaComments: true);
   }
 
   Future<void> test_expression_bang_hint_with_cast() async {
     var content = 'int f(Object/*?*/ o) => o/*!*/;';
-    // TODO(paulberry): it would be better to remove the `/*` and `*/` so we
-    // would be left with `o! as int;`
-    var expected = 'int f(Object?/*?*/ o) => o! as int/*!*/;';
+    var expected = 'int f(Object? o) => o! as int;';
     await _checkSingleFileChanges(content, expected);
   }
 
@@ -1866,7 +1858,7 @@
 ''';
     var expected = '''
 class C {
-  int/*!*/ i;
+  int i;
   C(int this.i);
 }
 void f(int? i, bool b) {
@@ -2283,7 +2275,7 @@
     var expected = '''
 abstract class C {
   void Function(int?) f();
-  int?/*?*/ Function() g();
+  int? Function() g();
 }
 int? test(C c) {
   c.f()(null);
@@ -2307,7 +2299,7 @@
     var expected = '''
 abstract class C {
   void Function(int?) get f;
-  int?/*?*/ Function() get g;
+  int? Function() get g;
 }
 int? test(C c) {
   c.f(null);
@@ -2382,9 +2374,9 @@
 import 'dart:async';
 void f(
     FutureOr<int> foi1,
-    FutureOr<int?/*?*/> foi2,
-    FutureOr<int>?/*?*/ foi3,
-    FutureOr<int?/*?*/>?/*?*/ foi4
+    FutureOr<int?> foi2,
+    FutureOr<int>? foi3,
+    FutureOr<int?>? foi4
 ) {
   int i1 = foi1 as int;
   int? i2 = foi2 as int?;
@@ -2486,7 +2478,7 @@
 ''';
     var expected = '''
 class C {
-  final Comparator<int/*!*/> comparison;
+  final Comparator<int> comparison;
   C(int Function(int, int) comparison) : comparison = comparison;
   void test() {
     comparison(f()!, f()!);
@@ -2593,7 +2585,7 @@
   final T? value;
   A(this.value);
 }
-class C implements A<String/*!*/> {
+class C implements A<String> {
   String? get value => false ? "y" : null;
 }
 ''';
@@ -2630,7 +2622,7 @@
   x.add(null);
 }
 void g() {
-  f(<int/*!*/>[]);
+  f(<int>[]);
 }
 ''';
     await _checkSingleFileChanges(content, expected);
@@ -2668,7 +2660,7 @@
     // unnecessarily force B.f's parameter type to be nullable.
     var expected = '''
 abstract class A {
-  void f(int?/*?*/ i);
+  void f(int? i);
 }
 abstract class B {
   void f(int i);
@@ -2697,7 +2689,7 @@
     // B.f's return type to be nullable.
     var expected = '''
 abstract class A {
-  int?/*?*/ f();
+  int? f();
 }
 abstract class B {
   int f();
@@ -2860,8 +2852,8 @@
 }
 ''';
     var expected = '''
-class C<T extends Object/*!*/> {
-  C(T/*!*/ t);
+class C<T extends Object> {
+  C(T t);
 }
 main() {
   C<int> c = C<int>(null!);
@@ -2882,7 +2874,7 @@
 ''';
     var expected = '''
 class C<T> {
-  C(T/*!*/ t);
+  C(T t);
 }
 main() {
   C<int?> c = C<int?>(null);
@@ -2901,8 +2893,8 @@
 }
 ''';
     var expected = '''
-class C<T extends Object/*!*/> {
-  C(T/*!*/ t);
+class C<T extends Object> {
+  C(T t);
 }
 main() {
   C<int> c = C(null!);
@@ -2923,7 +2915,7 @@
 ''';
     var expected = '''
 class C<T> {
-  C(T/*!*/ t);
+  C(T t);
 }
 main() {
   C<int?> c = C(null);
@@ -3041,12 +3033,10 @@
   int g() => x;
 }
 ''';
-    // TODO(paulberry): it would be better to just replace the comment with the
-    // word `late`.
     var expected = '''
 class C {
   C();
-  /*late*/ late int x;
+  late int x;
   f() {
     x = 1;
   }
@@ -3066,11 +3056,9 @@
   int g() => x;
 }
 ''';
-    // TODO(paulberry): it would be better to just replace the comment with the
-    // word `late`.
     var expected = '''
 class C {
-  /*late*/ late int x;
+  late int x;
   f() {
     x = 1;
   }
@@ -3093,11 +3081,9 @@
   return 0;
 }
 ''';
-    // TODO(paulberry): it would be better to just replace the comment with the
-    // word `late`.
     var expected = '''
 int f(bool b1, bool b2) {
-  /*late*/ late int x;
+  late int x;
   if (b1) {
     x = 1;
   }
@@ -3120,11 +3106,9 @@
   int g() => x;
 }
 ''';
-    // TODO(paulberry): it would be better to just replace the comment with the
-    // word `late`.
     var expected = '''
 class C {
-  static /*late*/ late int x;
+  static late int x;
   f() {
     x = 1;
   }
@@ -3142,10 +3126,8 @@
 }
 int g() => x;
 ''';
-    // TODO(paulberry): it would be better to just replace the comment with the
-    // word `late`.
     var expected = '''
-/*late*/ late int x;
+late int x;
 f() {
   x = 1;
 }
@@ -3396,7 +3378,7 @@
 }
 ''';
     var expected = '''
-T f<T extends Object/*!*/>(T/*!*/ t) => t;
+T f<T extends Object>(T t) => t;
 void g() {
   int x = f<int>(null!);
 }
@@ -3413,7 +3395,7 @@
 }
 ''';
     var expected = '''
-T f<T>(T/*!*/ t) => t;
+T f<T>(T t) => t;
 void g() {
   int? x = f<int?>(null);
 }
@@ -3446,7 +3428,7 @@
 }
 ''';
     var expected = '''
-T f<T extends Object/*!*/>(T/*!*/ t) => t;
+T f<T extends Object>(T t) => t;
 void g() {
   int x = f(null!);
 }
@@ -3463,7 +3445,7 @@
 }
 ''';
     var expected = '''
-T f<T>(T/*!*/ t) => t;
+T f<T>(T t) => t;
 void g() {
   int? x = f(null);
 }
@@ -3781,10 +3763,10 @@
 ''';
     var expected = '''
 abstract class C {
-  void f(List<int/*!*/> x, int y) {
+  void f(List<int> x, int y) {
     x.add(y);
   }
-  int?/*?*/ g();
+  int? g();
   void test() {
     f(<int>[], g()!);
   }
@@ -3806,7 +3788,7 @@
     // safely passed to f.
     var expected = '''
 Iterable<int> f(List<int?> x) => x.map(g as int Function(int?));
-int g(int/*!*/ x) => x + 1;
+int g(int x) => x + 1;
 main() {
   f([null]);
 }
@@ -3926,8 +3908,8 @@
 class _C {
   f() {}
 }
-_C g(int/*!*/ i) => _C();
-test(int?/*?*/ j) {
+_C g(int i) => _C();
+test(int? j) {
   g(j!)..f();
 }
 ''';
@@ -3943,7 +3925,7 @@
 }
 ''';
     var expected = '''
-abstract class C<E, T extends Iterable<E>?/*?*/> {
+abstract class C<E, T extends Iterable<E>?> {
   void f(T iter) {
     for(var i in iter!) {}
   }
@@ -4102,7 +4084,7 @@
 ''';
     var expected = '''
 abstract class Base {
-  int/*!*/ f();
+  int f();
 }
 class Derived extends Base {
   int f() => g()!;
@@ -4253,7 +4235,7 @@
   D<T> operator-();
 }
 class D<U> {}
-D<int?> test(C<int?/*?*/> c) => -c;
+D<int?> test(C<int?> c) => -c;
 ''';
     await _checkSingleFileChanges(content, expected);
   }
@@ -4324,7 +4306,7 @@
     // `x ??= [0]` promotes x from List<int?>? to List<int?>.  Since there is
     // still a `?` on the `int`, `x[0]` must be null checked.
     var expected = '''
-int/*!*/ f(List<int?/*?*/>?/*?*/ x) {
+int f(List<int?>? x) {
   x ??= [0];
   return x[0]!;
 }
@@ -4347,7 +4329,7 @@
     // return type of the function literal.  As a result, the reference to `x`
     // in the function literal is null checked.
     var expected = '''
-void f(int/*!*/ Function(int?) callback) {
+void f(int Function(int?) callback) {
   callback(null);
 }
 void test() {
@@ -4499,7 +4481,7 @@
 
   Future<void> test_remove_question_from_question_dot() async {
     var content = '_f(int/*!*/ i) => i?.isEven;';
-    var expected = '_f(int/*!*/ i) => i.isEven;';
+    var expected = '_f(int i) => i.isEven;';
     await _checkSingleFileChanges(content, expected);
   }
 
@@ -4512,16 +4494,16 @@
 ''';
     var expected = '''
 class C {
-  int?/*?*/ i;
+  int? i;
 }
-int/*!*/ f(C/*!*/ c) => c.i!;
+int f(C c) => c.i!;
 ''';
     await _checkSingleFileChanges(content, expected);
   }
 
   Future<void> test_remove_question_from_question_dot_method() async {
     var content = '_f(int/*!*/ i) => i?.abs();';
-    var expected = '_f(int/*!*/ i) => i.abs();';
+    var expected = '_f(int i) => i.abs();';
     await _checkSingleFileChanges(content, expected);
   }
 
@@ -4534,9 +4516,9 @@
 ''';
     var expected = '''
 class C {
-  int/*!*/ i;
+  int i;
 }
-bool?/*?*/ f(C?/*?*/ c) => c?.i.isEven;
+bool? f(C? c) => c?.i.isEven;
 ''';
     await _checkSingleFileChanges(content, expected);
   }
@@ -4910,7 +4892,7 @@
     var expected = '''
 typedef F = Function(int?);
 
-F/*!*/ _f;
+F _f;
 
 f() {
   _f(null);
@@ -4975,7 +4957,7 @@
     var expected = '''
 typedef F = Function<T>(T);
 
-F/*!*/ _f;
+F _f;
 
 f() {
   _f<int?>(null);
@@ -4997,7 +4979,7 @@
     var expected = '''
 typedef F<R> = Function<T>(T);
 
-F<Object>/*!*/ _f;
+F<Object> _f;
 
 f() {
   _f<int?>(null);
@@ -5019,7 +5001,7 @@
     var expected = '''
 typedef F<T> = Function(T);
 
-F<int?>/*!*/ _f;
+F<int?> _f;
 
 f() {
   _f(null);
@@ -5041,7 +5023,7 @@
     var expected = '''
 typedef F<T> = Function(T);
 
-F<int?>/*!*/ _f;
+F<int?> _f;
 
 f() {
   _f(null);
@@ -5077,7 +5059,7 @@
     var expected = '''
 typedef F(int? x);
 
-F/*!*/ _f;
+F _f;
 
 f() {
   _f(null);
@@ -5127,7 +5109,7 @@
     var expected = '''
 typedef F<T>(T t);
 
-F<int?>/*!*/ _f;
+F<int?> _f;
 
 f() {
   _f(null);
@@ -5149,7 +5131,7 @@
     var expected = '''
 typedef F<T>(T t);
 
-F<int?>/*!*/ _f;
+F<int?> _f;
 
 f() {
   _f(null);
diff --git a/pkg/nnbd_migration/test/edge_builder_test.dart b/pkg/nnbd_migration/test/edge_builder_test.dart
index bb1e7e2..8e55074 100644
--- a/pkg/nnbd_migration/test/edge_builder_test.dart
+++ b/pkg/nnbd_migration/test/edge_builder_test.dart
@@ -566,7 +566,7 @@
   }
 
   bool hasNullCheckHint(Expression expression) =>
-      variables.hasNullCheckHint(testSource, expression);
+      variables.getNullCheckHint(testSource, expression) != null;
 
   Future<void> test_already_migrated_field() async {
     await analyze('''
diff --git a/pkg/nnbd_migration/test/edit_plan_test.dart b/pkg/nnbd_migration/test/edit_plan_test.dart
index 120b97c..99c9b49 100644
--- a/pkg/nnbd_migration/test/edit_plan_test.dart
+++ b/pkg/nnbd_migration/test/edit_plan_test.dart
@@ -8,6 +8,7 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/source/line_info.dart';
 import 'package:nnbd_migration/src/edit_plan.dart';
+import 'package:nnbd_migration/src/utilities/hint_utils.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -58,6 +59,62 @@
   NodeProducingEditPlan extract(AstNode inner, AstNode outer) =>
       planner.extract(outer, planner.passThrough(inner));
 
+  Future<void> test_acceptLateHint() async {
+    var code = '/* late */ int x = 0;';
+    await analyze(code);
+    var hint = getPrefixHint(findNode.simple('int').token);
+    var changes = checkPlan(
+        planner.acceptLateHint(
+            planner.passThrough(findNode.simple('int')), hint),
+        'late int x = 0;');
+    expect(changes.keys, unorderedEquals([0, 7]));
+    expect(changes[7], hasLength(1));
+    expect(changes[7][0].length, 3);
+  }
+
+  Future<void> test_acceptLateHint_space_needed_after() async {
+    var code = '/* late */int x = 0;';
+    await analyze(code);
+    var hint = getPrefixHint(findNode.simple('int').token);
+    checkPlan(
+        planner.acceptLateHint(
+            planner.passThrough(findNode.simple('int')), hint),
+        'late int x = 0;');
+  }
+
+  Future<void> test_acceptLateHint_space_needed_before() async {
+    var code = '@deprecated/* late */ int x = 0;';
+    await analyze(code);
+    var hint = getPrefixHint(findNode.simple('int').token);
+    checkPlan(
+        planner.acceptLateHint(
+            planner.passThrough(findNode.simple('int')), hint),
+        '@deprecated late int x = 0;');
+  }
+
+  Future<void> test_acceptNullabilityOrNullCheckHint() async {
+    var code = 'int /*?*/ x = 0;';
+    await analyze(code);
+    var intRef = findNode.simple('int');
+    var typeName = planner.passThrough(intRef);
+    checkPlan(
+        planner.acceptNullabilityOrNullCheckHint(
+            typeName, getPostfixHint(intRef.token)),
+        'int? x = 0;');
+  }
+
+  Future<void> test_acceptNullabilityOrNullCheckHint_inside_extract() async {
+    var code = 'f(x) => 3 * x /*!*/ * 4;';
+    await analyze(code);
+    var xRef = findNode.simple('x /*');
+    checkPlan(
+        planner.extract(
+            xRef.parent.parent,
+            planner.acceptNullabilityOrNullCheckHint(
+                planner.passThrough(xRef), getPostfixHint(xRef.token))),
+        'f(x) => x!;');
+  }
+
   Future<void> test_addBinaryPostfix_assignment_right_associative() async {
     await analyze('_f(a, b, c) => a = b;');
     // Admittedly this is sort of a bogus test case, since the code it produces
@@ -288,6 +345,50 @@
     }
   }
 
+  Future<void> test_dropNullabilityHint() async {
+    var code = 'int /*!*/ x = 0;';
+    await analyze(code);
+    var intRef = findNode.simple('int');
+    var typeName = planner.passThrough(intRef);
+    checkPlan(
+        planner.dropNullabilityHint(typeName, getPostfixHint(intRef.token)),
+        'int x = 0;');
+  }
+
+  Future<void> test_dropNullabilityHint_space_before_must_be_kept() async {
+    var code = 'int /*!*/x = 0;';
+    await analyze(code);
+    var intRef = findNode.simple('int');
+    var typeName = planner.passThrough(intRef);
+    var changes = checkPlan(
+        planner.dropNullabilityHint(typeName, getPostfixHint(intRef.token)),
+        'int x = 0;');
+    expect(changes.keys, unorderedEquals([code.indexOf('/*')]));
+  }
+
+  Future<void> test_dropNullabilityHint_space_needed() async {
+    var code = 'int/*!*/x = 0;';
+    await analyze(code);
+    var intRef = findNode.simple('int');
+    var typeName = planner.passThrough(intRef);
+    checkPlan(
+        planner.dropNullabilityHint(typeName, getPostfixHint(intRef.token)),
+        'int x = 0;');
+  }
+
+  Future<void> test_dropNullabilityHint_tight_no_space_needed() async {
+    // We try to minimize how much we alter the source code, so we don't insert
+    // a space in this example even though it would look prettier to do so.
+    var code = 'void Function()/*!*/x = () {};';
+    await analyze(code);
+    var functionType = findNode.genericFunctionType('Function');
+    var typeName = planner.passThrough(functionType);
+    checkPlan(
+        planner.dropNullabilityHint(
+            typeName, getPostfixHint(functionType.endToken)),
+        'void Function()x = () {};');
+  }
+
   Future<void> test_explainNonNullable() async {
     await analyze('int x = 0;');
     checkPlan(
diff --git a/pkg/nnbd_migration/test/fix_builder_test.dart b/pkg/nnbd_migration/test/fix_builder_test.dart
index dd306f7..8b320b7 100644
--- a/pkg/nnbd_migration/test/fix_builder_test.dart
+++ b/pkg/nnbd_migration/test/fix_builder_test.dart
@@ -49,13 +49,12 @@
 
   static final isMakeNullable = TypeMatcher<NodeChangeForTypeAnnotation>()
       .having((c) => c.makeNullable, 'makeNullable', true)
-      .having((c) => c.makeNullableDueToHint, 'makeNullableDueToHint', false);
+      .having((c) => c.nullabilityHint, 'nullabilityHint', isNull);
 
   static final isMakeNullableDueToHint =
       TypeMatcher<NodeChangeForTypeAnnotation>()
           .having((c) => c.makeNullable, 'makeNullable', true)
-          .having(
-              (c) => c.makeNullableDueToHint, 'makeNullableDueToHint', true);
+          .having((c) => c.nullabilityHint, 'nullabilityHint', isNotNull);
 
   static const isEdge = TypeMatcher<NullabilityEdge>();
 
diff --git a/pkg/nnbd_migration/test/instrumentation_test.dart b/pkg/nnbd_migration/test/instrumentation_test.dart
index e6c1681..67cce5e 100644
--- a/pkg/nnbd_migration/test/instrumentation_test.dart
+++ b/pkg/nnbd_migration/test/instrumentation_test.dart
@@ -149,6 +149,13 @@
     migration.finish();
   }
 
+  void assertEdit(AtomicEdit edit,
+      {dynamic description = anything, dynamic fixReasons = anything}) {
+    var info = edit.info;
+    expect(info.description, description);
+    expect(info.fixReasons, fixReasons);
+  }
+
   Future<void> test_explicitTypeNullability() async {
     var content = '''
 int x = 1;
@@ -209,79 +216,78 @@
   }
 
   Future<void> test_fix_reason_add_required_function() async {
-    await analyze('_f({int/*!*/ i) {}');
+    var content = '_f({int/*!*/ i) {}';
+    await analyze(content);
     var intAnnotation = findNode.typeAnnotation('int');
-    expect(changes, isNotEmpty);
-    for (var change in changes.values) {
-      expect(change, isNotEmpty);
-      for (var edit in change) {
-        var info = edit.info;
-        expect(info.description,
-            NullabilityFixDescription.addRequired(null, '_f', 'i'));
-        expect(info.fixReasons[FixReasonTarget.root],
-            same(explicitTypeNullability[intAnnotation]));
-      }
-    }
+    var intPos = content.indexOf('int');
+    var commentPos = content.indexOf('/*');
+    expect(changes.keys, unorderedEquals([intPos, commentPos]));
+    assertEdit(changes[intPos].single,
+        description: NullabilityFixDescription.addRequired(null, '_f', 'i'),
+        fixReasons: {
+          FixReasonTarget.root: same(explicitTypeNullability[intAnnotation])
+        });
   }
 
   Future<void> test_fix_reason_add_required_method() async {
-    await analyze('class C { _f({int/*!*/ i) {} }');
+    var content = 'class C { _f({int/*!*/ i) {} }';
+    await analyze(content);
     var intAnnotation = findNode.typeAnnotation('int');
-    expect(changes, isNotEmpty);
-    for (var change in changes.values) {
-      expect(change, isNotEmpty);
-      for (var edit in change) {
-        var info = edit.info;
-        expect(info.description,
-            NullabilityFixDescription.addRequired('C', '_f', 'i'));
-        expect(info.fixReasons[FixReasonTarget.root],
-            same(explicitTypeNullability[intAnnotation]));
-      }
-    }
+    var intPos = content.indexOf('int');
+    var commentPos = content.indexOf('/*');
+    expect(changes.keys, unorderedEquals([intPos, commentPos]));
+    assertEdit(changes[intPos].single,
+        description: NullabilityFixDescription.addRequired('C', '_f', 'i'),
+        fixReasons: {
+          FixReasonTarget.root: same(explicitTypeNullability[intAnnotation])
+        });
   }
 
   Future<void> test_fix_reason_discard_condition() async {
-    await analyze('''
+    var content = '''
 _f(int/*!*/ i) {
   if (i != null) {
     return i;
   }
 }
-''');
+''';
+    await analyze(content);
     var intAnnotation = findNode.typeAnnotation('int');
-    expect(changes, isNotEmpty);
-    for (var change in changes.values) {
-      expect(change, isNotEmpty);
-      for (var edit in change) {
-        var info = edit.info;
-        expect(info.description, NullabilityFixDescription.discardCondition);
-        expect(info.fixReasons[FixReasonTarget.root],
-            same(explicitTypeNullability[intAnnotation]));
-      }
-    }
+    var commentPos = content.indexOf('/*');
+    var ifPos = content.indexOf('if');
+    var afterReturnPos = content.indexOf('i;') + 2;
+    expect(changes.keys, unorderedEquals([commentPos, ifPos, afterReturnPos]));
+    var expectedFixReasons = {
+      FixReasonTarget.root: same(explicitTypeNullability[intAnnotation])
+    };
+    assertEdit(changes[ifPos].single,
+        description: NullabilityFixDescription.discardCondition,
+        fixReasons: expectedFixReasons);
+    assertEdit(changes[afterReturnPos].single,
+        description: NullabilityFixDescription.discardCondition,
+        fixReasons: expectedFixReasons);
   }
 
   Future<void> test_fix_reason_discard_condition_no_block() async {
-    await analyze('''
+    var content = '''
 _f(int/*!*/ i) {
   if (i != null) return i;
 }
-''');
+''';
+    await analyze(content);
     var intAnnotation = findNode.typeAnnotation('int');
-    expect(changes, isNotEmpty);
-    for (var change in changes.values) {
-      expect(change, isNotEmpty);
-      for (var edit in change) {
-        var info = edit.info;
-        expect(info.description, NullabilityFixDescription.discardCondition);
-        expect(info.fixReasons[FixReasonTarget.root],
-            same(explicitTypeNullability[intAnnotation]));
-      }
-    }
+    var commentPos = content.indexOf('/*');
+    var ifPos = content.indexOf('if');
+    expect(changes.keys, unorderedEquals([commentPos, ifPos]));
+    assertEdit(changes[ifPos].single,
+        description: NullabilityFixDescription.discardCondition,
+        fixReasons: {
+          FixReasonTarget.root: same(explicitTypeNullability[intAnnotation])
+        });
   }
 
   Future<void> test_fix_reason_discard_else() async {
-    await analyze('''
+    var content = '''
 _f(int/*!*/ i) {
   if (i != null) {
     return i;
@@ -289,47 +295,46 @@
     return 'null';
   }
 }
-''');
+''';
+    await analyze(content);
     var intAnnotation = findNode.typeAnnotation('int');
-    expect(changes, hasLength(2));
-    // Change #1: drop the if-condition.
-    var dropCondition = changes[findNode.statement('if').offset].single;
-    expect(dropCondition.isDeletion, true);
-    expect(dropCondition.info.description,
-        NullabilityFixDescription.discardCondition);
-    expect(dropCondition.info.fixReasons[FixReasonTarget.root],
-        same(explicitTypeNullability[intAnnotation]));
-    // Change #2: drop the else.
-    var dropElse = changes[findNode.statement('return i').end].single;
-    expect(dropElse.isDeletion, true);
-    expect(dropElse.info.description, NullabilityFixDescription.discardElse);
-    expect(dropElse.info.fixReasons[FixReasonTarget.root],
-        same(explicitTypeNullability[intAnnotation]));
+    var commentPos = content.indexOf('/*');
+    var ifPos = content.indexOf('if');
+    var afterReturnPos = content.indexOf('i;') + 2;
+    expect(changes.keys, unorderedEquals([commentPos, ifPos, afterReturnPos]));
+    var expectedFixReasons = {
+      FixReasonTarget.root: same(explicitTypeNullability[intAnnotation])
+    };
+    assertEdit(changes[ifPos].single,
+        description: NullabilityFixDescription.discardCondition,
+        fixReasons: expectedFixReasons);
+    assertEdit(changes[afterReturnPos].single,
+        description: NullabilityFixDescription.discardElse,
+        fixReasons: expectedFixReasons);
   }
 
   Future<void> test_fix_reason_discard_else_empty_then() async {
-    await analyze('''
+    var content = '''
 _f(int/*!*/ i) {
   if (i != null) {} else {
     return 'null';
   }
 }
-''');
+''';
+    await analyze(content);
     var intAnnotation = findNode.typeAnnotation('int');
-    expect(changes, isNotEmpty);
-    for (var change in changes.values) {
-      expect(change, isNotEmpty);
-      for (var edit in change) {
-        var info = edit.info;
-        expect(info.description, NullabilityFixDescription.discardIf);
-        expect(info.fixReasons[FixReasonTarget.root],
-            same(explicitTypeNullability[intAnnotation]));
-      }
-    }
+    var commentPos = content.indexOf('/*');
+    var bodyPos = content.indexOf('i) {') + 4;
+    expect(changes.keys, unorderedEquals([commentPos, bodyPos]));
+    assertEdit(changes[bodyPos].single,
+        description: NullabilityFixDescription.discardIf,
+        fixReasons: {
+          FixReasonTarget.root: same(explicitTypeNullability[intAnnotation])
+        });
   }
 
   Future<void> test_fix_reason_discard_then() async {
-    await analyze('''
+    var content = '''
 _f(int/*!*/ i) {
   if (i == null) {
     return 'null';
@@ -337,39 +342,42 @@
     return i;
   }
 }
-''');
+''';
+    await analyze(content);
     var intAnnotation = findNode.typeAnnotation('int');
-    expect(changes, isNotEmpty);
-    for (var change in changes.values) {
-      expect(change, isNotEmpty);
-      for (var edit in change) {
-        var info = edit.info;
-        expect(info.description, NullabilityFixDescription.discardThen);
-        expect(info.fixReasons[FixReasonTarget.root],
-            same(explicitTypeNullability[intAnnotation]));
-      }
-    }
+    var commentPos = content.indexOf('/*');
+    var ifPos = content.indexOf('if');
+    var afterReturnPos = content.indexOf('i;') + 2;
+    expect(changes.keys, unorderedEquals([commentPos, ifPos, afterReturnPos]));
+    var expectedFixReasons = {
+      FixReasonTarget.root: same(explicitTypeNullability[intAnnotation])
+    };
+    assertEdit(changes[ifPos].single,
+        description: NullabilityFixDescription.discardThen,
+        fixReasons: expectedFixReasons);
+    assertEdit(changes[afterReturnPos].single,
+        description: NullabilityFixDescription.discardThen,
+        fixReasons: expectedFixReasons);
   }
 
   Future<void> test_fix_reason_discard_then_no_else() async {
-    await analyze('''
+    var content = '''
 _f(int/*!*/ i) {
   if (i == null) {
     return 'null';
   }
 }
-''');
+''';
+    await analyze(content);
     var intAnnotation = findNode.typeAnnotation('int');
-    expect(changes, isNotEmpty);
-    for (var change in changes.values) {
-      expect(change, isNotEmpty);
-      for (var edit in change) {
-        var info = edit.info;
-        expect(info.description, NullabilityFixDescription.discardIf);
-        expect(info.fixReasons[FixReasonTarget.root],
-            same(explicitTypeNullability[intAnnotation]));
-      }
-    }
+    var commentPos = content.indexOf('/*');
+    var bodyPos = content.indexOf('i) {') + 4;
+    expect(changes.keys, unorderedEquals([commentPos, bodyPos]));
+    assertEdit(changes[bodyPos].single,
+        description: NullabilityFixDescription.discardIf,
+        fixReasons: {
+          FixReasonTarget.root: same(explicitTypeNullability[intAnnotation])
+        });
   }
 
   Future<void> test_fix_reason_edge() async {
@@ -423,30 +431,26 @@
   }
 
   Future<void> test_fix_reason_remove_question_from_question_dot() async {
-    await analyze('_f(int/*!*/ i) => i?.isEven;');
-    expect(changes, isNotEmpty);
-    for (var change in changes.values) {
-      expect(change, isNotEmpty);
-      for (var edit in change) {
-        var info = edit.info;
-        expect(info.description, NullabilityFixDescription.removeNullAwareness);
-        expect(info.fixReasons, isEmpty);
-      }
-    }
+    var content = '_f(int/*!*/ i) => i?.isEven;';
+    await analyze(content);
+    var commentPos = content.indexOf('/*');
+    var questionDotPos = content.indexOf('?.');
+    expect(changes.keys, unorderedEquals([commentPos, questionDotPos]));
+    assertEdit(changes[questionDotPos].single,
+        description: NullabilityFixDescription.removeNullAwareness,
+        fixReasons: isEmpty);
   }
 
   Future<void>
       test_fix_reason_remove_question_from_question_dot_method() async {
-    await analyze('_f(int/*!*/ i) => i?.abs();');
-    expect(changes, isNotEmpty);
-    for (var change in changes.values) {
-      expect(change, isNotEmpty);
-      for (var edit in change) {
-        var info = edit.info;
-        expect(info.description, NullabilityFixDescription.removeNullAwareness);
-        expect(info.fixReasons, isEmpty);
-      }
-    }
+    var content = '_f(int/*!*/ i) => i?.abs();';
+    await analyze(content);
+    var commentPos = content.indexOf('/*');
+    var questionDotPos = content.indexOf('?.');
+    expect(changes.keys, unorderedEquals([commentPos, questionDotPos]));
+    assertEdit(changes[questionDotPos].single,
+        description: NullabilityFixDescription.removeNullAwareness,
+        fixReasons: isEmpty);
   }
 
   Future<void> test_fix_reason_remove_unnecessary_cast() async {
diff --git a/pkg/nnbd_migration/test/node_builder_test.dart b/pkg/nnbd_migration/test/node_builder_test.dart
index 082f902..adbd403 100644
--- a/pkg/nnbd_migration/test/node_builder_test.dart
+++ b/pkg/nnbd_migration/test/node_builder_test.dart
@@ -1716,33 +1716,33 @@
   Future<void> test_variableDeclaration_late_hint_after_metadata() async {
     await analyze('@deprecated /*late*/ int i;');
     expect(
-        variables.isLateHinted(
+        variables.getLateHint(
             testSource, findNode.variableDeclarationList('int i')),
-        true);
+        isNotNull);
   }
 
   Future<void> test_variableDeclaration_late_hint_multiple_comments() async {
     await analyze('/*other*/ /*late*/ int i;');
     expect(
-        variables.isLateHinted(
+        variables.getLateHint(
             testSource, findNode.variableDeclarationList('int i')),
-        true);
+        isNotNull);
   }
 
   Future<void> test_variableDeclaration_late_hint_simple() async {
     await analyze('/*late*/ int i;');
     expect(
-        variables.isLateHinted(
+        variables.getLateHint(
             testSource, findNode.variableDeclarationList('int i')),
-        true);
+        isNotNull);
   }
 
   Future<void> test_variableDeclaration_late_hint_with_spaces() async {
     await analyze('/* late */ int i;');
     expect(
-        variables.isLateHinted(
+        variables.getLateHint(
             testSource, findNode.variableDeclarationList('int i')),
-        true);
+        isNotNull);
   }
 
   Future<void> test_variableDeclaration_type_simple() async {
diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart
index 48605b1..834750e 100644
--- a/pkg/vm/lib/kernel_front_end.dart
+++ b/pkg/vm/lib/kernel_front_end.dart
@@ -96,6 +96,9 @@
       help:
           'Enable global type flow analysis and related transformations in AOT mode.',
       defaultsTo: true);
+  args.addFlag('tree-shake-write-only-fields',
+      help: 'Enable tree shaking of fields which are only written in AOT mode.',
+      defaultsTo: false);
   args.addFlag('protobuf-tree-shaker',
       help: 'Enable protobuf tree shaker transformation in AOT mode.',
       defaultsTo: false);
@@ -170,6 +173,7 @@
   final bool useProtobufTreeShaker = options['protobuf-tree-shaker'];
   final bool splitOutputByPackages = options['split-output-by-packages'];
   final bool minimalKernel = options['minimal-kernel'];
+  final bool treeShakeWriteOnlyFields = options['tree-shake-write-only-fields'];
   final List<String> experimentalFlags = options['enable-experiment'];
   final Map<String, String> environmentDefines = {};
 
@@ -246,7 +250,8 @@
       bytecodeOptions: bytecodeOptions,
       dropAST: dropAST && !splitOutputByPackages,
       useProtobufTreeShaker: useProtobufTreeShaker,
-      minimalKernel: minimalKernel);
+      minimalKernel: minimalKernel,
+      treeShakeWriteOnlyFields: treeShakeWriteOnlyFields);
 
   errorPrinter.printCompilationMessages();
 
@@ -322,7 +327,8 @@
     BytecodeOptions bytecodeOptions,
     bool dropAST: false,
     bool useProtobufTreeShaker: false,
-    bool minimalKernel: false}) async {
+    bool minimalKernel: false,
+    bool treeShakeWriteOnlyFields: false}) async {
   // Replace error handler to detect if there are compilation errors.
   final errorDetector =
       new ErrorDetector(previousErrorHandler: options.onDiagnostic);
@@ -348,7 +354,8 @@
         enableAsserts,
         useProtobufTreeShaker,
         errorDetector,
-        minimalKernel: minimalKernel);
+        minimalKernel: minimalKernel,
+        treeShakeWriteOnlyFields: treeShakeWriteOnlyFields);
 
     if (minimalKernel) {
       // compiledSources is component.uriToSource.keys.
@@ -424,7 +431,8 @@
     bool enableAsserts,
     bool useProtobufTreeShaker,
     ErrorDetector errorDetector,
-    {bool minimalKernel: false}) async {
+    {bool minimalKernel: false,
+    bool treeShakeWriteOnlyFields: false}) async {
   if (errorDetector.hasCompilationErrors) return;
 
   final coreTypes = new CoreTypes(component);
@@ -443,7 +451,8 @@
 
   if (useGlobalTypeFlowAnalysis) {
     globalTypeFlow.transformComponent(target, coreTypes, component,
-        treeShakeSignatures: !minimalKernel);
+        treeShakeSignatures: !minimalKernel,
+        treeShakeWriteOnlyFields: treeShakeWriteOnlyFields);
   } else {
     devirtualization.transformComponent(coreTypes, component);
     no_dynamic_invocations_annotator.transformComponent(component);
@@ -458,7 +467,8 @@
         component, coreTypes, null);
 
     globalTypeFlow.transformComponent(target, coreTypes, component,
-        treeShakeSignatures: !minimalKernel);
+        treeShakeSignatures: !minimalKernel,
+        treeShakeWriteOnlyFields: treeShakeWriteOnlyFields);
   }
 
   // TODO(35069): avoid recomputing CSA by reading it from the platform files.
diff --git a/pkg/vm/lib/transformations/type_flow/analysis.dart b/pkg/vm/lib/transformations/type_flow/analysis.dart
index 6d591f5..d9ace1d 100644
--- a/pkg/vm/lib/transformations/type_flow/analysis.dart
+++ b/pkg/vm/lib/transformations/type_flow/analysis.dart
@@ -191,12 +191,17 @@
       case CallKind.PropertyGet:
         assertx(args.values.length == firstParamIndex);
         assertx(args.names.isEmpty);
+        fieldValue.isGetterUsed = true;
         return fieldValue.getValue(
             typeFlowAnalysis, field.isStatic ? null : args.values[0]);
 
       case CallKind.PropertySet:
+      case CallKind.SetFieldInConstructor:
         assertx(args.values.length == firstParamIndex + 1);
         assertx(args.names.isEmpty);
+        if (selector.callKind == CallKind.PropertySet) {
+          fieldValue.isSetterUsed = true;
+        }
         final Type setterArg = args.values[firstParamIndex];
         fieldValue.setValue(
             setterArg, typeFlowAnalysis, field.isStatic ? null : args.receiver);
@@ -206,6 +211,7 @@
         // Call via field.
         // TODO(alexmarkov): support function types and use inferred type
         // to get more precise return type.
+        fieldValue.isGetterUsed = true;
         final receiver = fieldValue.getValue(
             typeFlowAnalysis, field.isStatic ? null : args.values[0]);
         if (receiver != const EmptyType()) {
@@ -775,6 +781,12 @@
   /// Flag indicating if field initializer was executed.
   bool isInitialized = false;
 
+  /// Flag indicating if field getter was executed.
+  bool isGetterUsed = false;
+
+  /// Flag indicating if field setter was executed.
+  bool isSetterUsed = false;
+
   _FieldValue(this.field, this.typeGuardSummary, TypesBuilder typesBuilder)
       : staticType = typesBuilder.fromStaticType(field.type, true) {
     if (field.initializer == null && _isDefaultValueOfFieldObservable()) {
@@ -1396,6 +1408,26 @@
     return false;
   }
 
+  /// Returns true if analysis found that getter corresponding to the given
+  /// [field] could be executed.
+  bool isFieldGetterUsed(Field field) {
+    final fieldValue = _fieldValues[field];
+    if (fieldValue != null) {
+      return fieldValue.isGetterUsed;
+    }
+    return false;
+  }
+
+  /// Returns true if analysis found that setter corresponding to the given
+  /// [field] could be executed.
+  bool isFieldSetterUsed(Field field) {
+    final fieldValue = _fieldValues[field];
+    if (fieldValue != null) {
+      return fieldValue.isSetterUsed;
+    }
+    return false;
+  }
+
   bool isClassAllocated(Class c) => hierarchyCache.allocatedClasses.contains(c);
 
   Call callSite(TreeNode node) => summaryCollector.callSites[node];
diff --git a/pkg/vm/lib/transformations/type_flow/calls.dart b/pkg/vm/lib/transformations/type_flow/calls.dart
index 06b20ec..55ff9ef 100644
--- a/pkg/vm/lib/transformations/type_flow/calls.dart
+++ b/pkg/vm/lib/transformations/type_flow/calls.dart
@@ -16,7 +16,8 @@
   Method, // x.foo(..) or foo()
   PropertyGet, // ... x.foo ...
   PropertySet, // x.foo = ...
-  FieldInitializer,
+  FieldInitializer, // run initializer of a field
+  SetFieldInConstructor, // foo = ... in initializer list in a constructor
 }
 
 /// [Selector] encapsulates the way of calling (at the call site).
@@ -55,6 +56,7 @@
         return member.getterType;
       case CallKind.PropertySet:
       case CallKind.FieldInitializer:
+      case CallKind.SetFieldInConstructor:
         return const BottomType();
     }
     return null;
@@ -72,7 +74,8 @@
       case CallKind.PropertySet:
         return (member is Field) || ((member is Procedure) && member.isSetter);
       case CallKind.FieldInitializer:
-        return (member is Field);
+      case CallKind.SetFieldInConstructor:
+        return member is Field;
     }
     return false;
   }
@@ -84,6 +87,7 @@
       case CallKind.PropertyGet:
         return 'get ';
       case CallKind.PropertySet:
+      case CallKind.SetFieldInConstructor:
         return 'set ';
       case CallKind.FieldInitializer:
         return 'init ';
diff --git a/pkg/vm/lib/transformations/type_flow/summary_collector.dart b/pkg/vm/lib/transformations/type_flow/summary_collector.dart
index 46a0e48..a2bf551 100644
--- a/pkg/vm/lib/transformations/type_flow/summary_collector.dart
+++ b/pkg/vm/lib/transformations/type_flow/summary_collector.dart
@@ -817,6 +817,7 @@
         break;
 
       case CallKind.PropertySet:
+      case CallKind.SetFieldInConstructor:
         args.add(new Type.nullableAny());
         break;
 
@@ -2088,8 +2089,11 @@
   TypeExpr visitFieldInitializer(FieldInitializer node) {
     final value = _visit(node.value);
     final args = new Args<TypeExpr>([_receiver, value]);
-    _makeCall(node,
-        new DirectSelector(node.field, callKind: CallKind.PropertySet), args);
+    _makeCall(
+        node,
+        new DirectSelector(node.field,
+            callKind: CallKind.SetFieldInConstructor),
+        args);
     return null;
   }
 
diff --git a/pkg/vm/lib/transformations/type_flow/transformer.dart b/pkg/vm/lib/transformations/type_flow/transformer.dart
index 3a3d400..b24f12e 100644
--- a/pkg/vm/lib/transformations/type_flow/transformer.dart
+++ b/pkg/vm/lib/transformations/type_flow/transformer.dart
@@ -38,7 +38,9 @@
 /// Assumes strong mode and closed world.
 Component transformComponent(
     Target target, CoreTypes coreTypes, Component component,
-    {PragmaAnnotationParser matcher, bool treeShakeSignatures: true}) {
+    {PragmaAnnotationParser matcher,
+    bool treeShakeSignatures: true,
+    bool treeShakeWriteOnlyFields: true}) {
   void ignoreAmbiguousSupertypes(Class cls, Supertype a, Supertype b) {}
   final hierarchy = new ClassHierarchy(component, coreTypes,
       onAmbiguousSupertypes: ignoreAmbiguousSupertypes);
@@ -72,17 +74,20 @@
   final transformsStopWatch = new Stopwatch()..start();
 
   final treeShaker = new TreeShaker(component, typeFlowAnalysis,
-      treeShakeSignatures: treeShakeSignatures)
-    ..transformComponent(component);
+      treeShakeSignatures: treeShakeSignatures,
+      treeShakeWriteOnlyFields: treeShakeWriteOnlyFields);
+  treeShaker.transformComponent(component);
 
-  new TFADevirtualization(component, typeFlowAnalysis, hierarchy)
+  new TFADevirtualization(
+          component, typeFlowAnalysis, hierarchy, treeShaker.fieldMorpher)
       .visitComponent(component);
 
   final unboxingInfo = new UnboxingInfoManager(typeFlowAnalysis);
 
   _makePartition(component, typeFlowAnalysis, unboxingInfo);
 
-  new AnnotateKernel(component, typeFlowAnalysis, unboxingInfo)
+  new AnnotateKernel(
+          component, typeFlowAnalysis, treeShaker.fieldMorpher, unboxingInfo)
       .visitComponent(component);
 
   treeShaker.finalizeSignatures();
@@ -100,9 +105,10 @@
 /// Devirtualization based on results of type flow analysis.
 class TFADevirtualization extends Devirtualization {
   final TypeFlowAnalysis _typeFlowAnalysis;
+  final FieldMorpher fieldMorpher;
 
-  TFADevirtualization(
-      Component component, this._typeFlowAnalysis, ClassHierarchy hierarchy)
+  TFADevirtualization(Component component, this._typeFlowAnalysis,
+      ClassHierarchy hierarchy, this.fieldMorpher)
       : super(_typeFlowAnalysis.environment.coreTypes, component, hierarchy);
 
   @override
@@ -110,7 +116,8 @@
       {bool setter = false}) {
     final callSite = _typeFlowAnalysis.callSite(node);
     if (callSite != null) {
-      final Member singleTarget = callSite.monomorphicTarget;
+      final Member singleTarget = fieldMorpher
+          .getMorphedMember(callSite.monomorphicTarget, isSetter: setter);
       if (singleTarget != null) {
         return new DirectCallMetadata(
             singleTarget, callSite.isNullableReceiver);
@@ -123,6 +130,7 @@
 /// Annotates kernel AST with metadata using results of type flow analysis.
 class AnnotateKernel extends RecursiveVisitor<Null> {
   final TypeFlowAnalysis _typeFlowAnalysis;
+  final FieldMorpher fieldMorpher;
   final DirectCallMetadataRepository _directCallMetadataRepository;
   final InferredTypeMetadataRepository _inferredTypeMetadata;
   final UnreachableNodeMetadataRepository _unreachableNodeMetadata;
@@ -134,8 +142,8 @@
   final Class _intClass;
   Constant _nullConstant;
 
-  AnnotateKernel(
-      Component component, this._typeFlowAnalysis, this._unboxingInfo)
+  AnnotateKernel(Component component, this._typeFlowAnalysis, this.fieldMorpher,
+      this._unboxingInfo)
       : _directCallMetadataRepository =
             component.metadata[DirectCallMetadataRepository.repositoryTag],
         _inferredTypeMetadata = new InferredTypeMetadataRepository(),
@@ -328,7 +336,8 @@
 
         // TODO(alexmarkov): figure out how to pass receiver type.
       }
-    } else if (!member.isAbstract) {
+    } else if (!member.isAbstract &&
+        !fieldMorpher.isExtraMemberWithReachableBody(member)) {
       _setUnreachable(member);
     } else if (member is! Field) {
       final unboxingInfoMetadata =
@@ -357,14 +366,15 @@
     // interface target, and table dispatch calls need selector IDs for all
     // interface targets.
     if (member.isInstanceMember) {
+      final original = fieldMorpher.getOriginalMember(member);
       final attrs = new ProcedureAttributesMetadata(
           methodOrSetterCalledDynamically:
-              _typeFlowAnalysis.isCalledDynamically(member),
+              _typeFlowAnalysis.isCalledDynamically(original),
           getterCalledDynamically:
-              _typeFlowAnalysis.isGetterCalledDynamically(member),
-          hasThisUses: _typeFlowAnalysis.isCalledViaThis(member),
-          hasNonThisUses: _typeFlowAnalysis.isCalledNotViaThis(member),
-          hasTearOffUses: _typeFlowAnalysis.isTearOffTaken(member),
+              _typeFlowAnalysis.isGetterCalledDynamically(original),
+          hasThisUses: _typeFlowAnalysis.isCalledViaThis(original),
+          hasNonThisUses: _typeFlowAnalysis.isCalledNotViaThis(original),
+          hasTearOffUses: _typeFlowAnalysis.isTearOffTaken(original),
           methodOrSetterSelectorId:
               _tableSelectorAssigner.methodOrSetterSelectorId(member),
           getterSelectorId: _tableSelectorAssigner.getterSelectorId(member));
@@ -558,11 +568,13 @@
 ///
 class TreeShaker {
   final TypeFlowAnalysis typeFlowAnalysis;
+  final bool treeShakeWriteOnlyFields;
   final Set<Class> _usedClasses = new Set<Class>();
   final Set<Class> _classesUsedInType = new Set<Class>();
   final Set<Member> _usedMembers = new Set<Member>();
   final Set<Extension> _usedExtensions = new Set<Extension>();
   final Set<Typedef> _usedTypedefs = new Set<Typedef>();
+  FieldMorpher fieldMorpher;
   _TreeShakerTypeVisitor typeVisitor;
   _TreeShakerConstantVisitor constantVisitor;
   _TreeShakerPass1 _pass1;
@@ -570,7 +582,8 @@
   _SignatureShaker _signatureShaker;
 
   TreeShaker(Component component, this.typeFlowAnalysis,
-      {bool treeShakeSignatures: true}) {
+      {bool treeShakeSignatures: true, this.treeShakeWriteOnlyFields: true}) {
+    fieldMorpher = new FieldMorpher(this);
     typeVisitor = new _TreeShakerTypeVisitor(this);
     constantVisitor = new _TreeShakerConstantVisitor(this, typeVisitor);
     _pass1 = new _TreeShakerPass1(this);
@@ -595,13 +608,28 @@
   bool isClassAllocated(Class c) => typeFlowAnalysis.isClassAllocated(c);
   bool isMemberUsed(Member m) => _usedMembers.contains(m);
   bool isExtensionUsed(Extension e) => _usedExtensions.contains(e);
-  bool isMemberBodyReachable(Member m) => typeFlowAnalysis.isMemberUsed(m);
+  bool isMemberBodyReachable(Member m) =>
+      typeFlowAnalysis.isMemberUsed(m) ||
+      fieldMorpher.isExtraMemberWithReachableBody(m);
   bool isFieldInitializerReachable(Field f) =>
       typeFlowAnalysis.isFieldInitializerUsed(f);
+  bool isFieldGetterReachable(Field f) => typeFlowAnalysis.isFieldGetterUsed(f);
+  bool isFieldSetterReachable(Field f) => typeFlowAnalysis.isFieldSetterUsed(f);
   bool isMemberReferencedFromNativeCode(Member m) =>
       typeFlowAnalysis.nativeCodeOracle.isMemberReferencedFromNativeCode(m);
   bool isTypedefUsed(Typedef t) => _usedTypedefs.contains(t);
 
+  bool retainField(Field f) =>
+      isMemberBodyReachable(f) &&
+          (!treeShakeWriteOnlyFields ||
+              isFieldGetterReachable(f) ||
+              (!f.isStatic &&
+                  f.initializer != null &&
+                  isFieldInitializerReachable(f) &&
+                  mayHaveSideEffects(f.initializer)) ||
+              (f.isLate && f.isFinal)) ||
+      isMemberReferencedFromNativeCode(f);
+
   void addClassUsedInType(Class c) {
     if (_classesUsedInType.add(c)) {
       if (kPrintDebug) {
@@ -637,9 +665,15 @@
       } else if (m is Procedure) {
         func = m.function;
         if (m.forwardingStubSuperTarget != null) {
+          m.forwardingStubSuperTarget = fieldMorpher.adjustInstanceCallTarget(
+              m.forwardingStubSuperTarget,
+              isSetter: m.isSetter);
           addUsedMember(m.forwardingStubSuperTarget);
         }
         if (m.forwardingStubInterfaceTarget != null) {
+          m.forwardingStubInterfaceTarget = fieldMorpher
+              .adjustInstanceCallTarget(m.forwardingStubInterfaceTarget,
+                  isSetter: m.isSetter);
           addUsedMember(m.forwardingStubInterfaceTarget);
         }
       } else if (m is Constructor) {
@@ -692,6 +726,86 @@
   }
 }
 
+class FieldMorpher {
+  final TreeShaker shaker;
+  final Set<Member> _extraMembersWithReachableBody = <Member>{};
+  final Map<Field, Member> _gettersForRemovedFields = <Field, Member>{};
+  final Map<Field, Member> _settersForRemovedFields = <Field, Member>{};
+  final Map<Member, Field> _removedFields = <Member, Field>{};
+
+  FieldMorpher(this.shaker);
+
+  Member _createAccessorForRemovedField(Field field, bool isSetter) {
+    assertx(!field.isStatic);
+    assertx(!shaker.retainField(field));
+    Procedure accessor;
+    if (isSetter) {
+      final isAbstract = !shaker.isFieldSetterReachable(field);
+      final parameter = new VariableDeclaration('value', type: field.type)
+        ..isCovariant = field.isCovariant
+        ..isGenericCovariantImpl = field.isGenericCovariantImpl
+        ..fileOffset = field.fileOffset;
+      accessor = new Procedure(
+          field.name,
+          ProcedureKind.Setter,
+          new FunctionNode(null,
+              positionalParameters: [parameter], returnType: const VoidType())
+            ..fileOffset = field.fileOffset,
+          isAbstract: isAbstract,
+          fileUri: field.fileUri);
+      if (!isAbstract) {
+        _extraMembersWithReachableBody.add(accessor);
+      }
+    } else {
+      accessor = new Procedure(field.name, ProcedureKind.Getter,
+          new FunctionNode(null, returnType: field.type),
+          isAbstract: true, fileUri: field.fileUri);
+    }
+    accessor.fileOffset = field.fileOffset;
+    field.enclosingClass.addMember(accessor);
+    _removedFields[accessor] = field;
+    shaker.addUsedMember(accessor);
+    return accessor;
+  }
+
+  /// Return a replacement for an instance call target.
+  /// If necessary, creates a getter or setter as a replacement if target is a
+  /// field which is going to be removed by the tree shaker.
+  /// This method is used during tree shaker pass 1.
+  Member adjustInstanceCallTarget(Member target, {bool isSetter = false}) {
+    if (target is Field && !shaker.retainField(target)) {
+      final targets =
+          isSetter ? _settersForRemovedFields : _gettersForRemovedFields;
+      return targets[target] ??=
+          _createAccessorForRemovedField(target, isSetter);
+    }
+    return target;
+  }
+
+  bool isExtraMemberWithReachableBody(Member member) =>
+      _extraMembersWithReachableBody.contains(member);
+
+  /// Return a member which replaced [target] in instance calls.
+  /// This method can be used after tree shaking to discover replacement.
+  Member getMorphedMember(Member target, {bool isSetter = false}) {
+    if (target == null) {
+      return null;
+    }
+    final targets =
+        isSetter ? _settersForRemovedFields : _gettersForRemovedFields;
+    return targets[target] ?? target;
+  }
+
+  /// Return original member which was replaced by [target] in instance calls.
+  /// This method can be used after tree shaking.
+  Member getOriginalMember(Member target) {
+    if (target == null) {
+      return null;
+    }
+    return _removedFields[target] ?? target;
+  }
+}
+
 /// Visits Dart types and collects all classes and typedefs used in types.
 /// This visitor is used during pass 1 of tree shaking. It is a separate
 /// visitor because [Transformer] does not provide a way to traverse types.
@@ -742,6 +856,7 @@
 /// transforms unreachable calls into 'throw' expressions.
 class _TreeShakerPass1 extends Transformer {
   final TreeShaker shaker;
+  final FieldMorpher fieldMorpher;
   final TypeEnvironment environment;
   final List<Initializer> additionalInitializers = [];
   Procedure _unsafeCast;
@@ -759,7 +874,8 @@
   }
 
   _TreeShakerPass1(this.shaker)
-      : environment = shaker.typeFlowAnalysis.environment;
+      : fieldMorpher = shaker.fieldMorpher,
+        environment = shaker.typeFlowAnalysis.environment;
 
   void transform(Component component) {
     component.transformChildren(this);
@@ -802,7 +918,9 @@
           'Attempt to execute code removed by Dart AOT compiler (TFA)'));
     }
     for (var arg in args.reversed) {
-      node = new Let(new VariableDeclaration(null, initializer: arg), node);
+      if (mayHaveSideEffects(arg)) {
+        node = new Let(new VariableDeclaration(null, initializer: arg), node);
+      }
     }
     Statistics.callsDropped++;
     return node;
@@ -894,7 +1012,7 @@
 
   @override
   TreeNode visitField(Field node) {
-    if (shaker.isMemberBodyReachable(node)) {
+    if (shaker.retainField(node)) {
       if (kPrintTrace) {
         tracePrint("Visiting $node");
       }
@@ -906,12 +1024,10 @@
           node.initializer = _makeUnreachableCall([])..parent = node;
         }
       }
-    } else if (shaker.isMemberReferencedFromNativeCode(node)) {
-      // Preserve members referenced from native code to satisfy lookups, even
-      // if they are not reachable. An instance member could be added via
-      // native code entry point but still unreachable if no instances of
-      // its enclosing class are allocated.
-      shaker.addUsedMember(node);
+    } else if (shaker.isFieldSetterReachable(node) && !node.isStatic) {
+      // Make sure setter is created to replace the field even if field is not
+      // used as an instance call target.
+      fieldMorpher.adjustInstanceCallTarget(node, isSetter: true);
     }
     return node;
   }
@@ -923,6 +1039,8 @@
       return _makeUnreachableCall(
           _flattenArguments(node.arguments, receiver: node.receiver));
     } else {
+      node.interfaceTarget =
+          fieldMorpher.adjustInstanceCallTarget(node.interfaceTarget);
       if (node.interfaceTarget != null) {
         shaker.addUsedMember(node.interfaceTarget);
       }
@@ -936,6 +1054,8 @@
     if (_isUnreachable(node)) {
       return _makeUnreachableCall([node.receiver]);
     } else {
+      node.interfaceTarget =
+          fieldMorpher.adjustInstanceCallTarget(node.interfaceTarget);
       if (node.interfaceTarget != null) {
         shaker.addUsedMember(node.interfaceTarget);
       }
@@ -949,6 +1069,8 @@
     if (_isUnreachable(node)) {
       return _makeUnreachableCall([node.receiver, node.value]);
     } else {
+      node.interfaceTarget = fieldMorpher
+          .adjustInstanceCallTarget(node.interfaceTarget, isSetter: true);
       if (node.interfaceTarget != null) {
         shaker.addUsedMember(node.interfaceTarget);
       }
@@ -962,6 +1084,8 @@
     if (_isUnreachable(node)) {
       return _makeUnreachableCall(_flattenArguments(node.arguments));
     } else {
+      node.interfaceTarget =
+          fieldMorpher.adjustInstanceCallTarget(node.interfaceTarget);
       if (node.interfaceTarget != null) {
         shaker.addUsedMember(node.interfaceTarget);
       }
@@ -975,6 +1099,8 @@
     if (_isUnreachable(node)) {
       return _makeUnreachableCall([]);
     } else {
+      node.interfaceTarget =
+          fieldMorpher.adjustInstanceCallTarget(node.interfaceTarget);
       if (node.interfaceTarget != null) {
         shaker.addUsedMember(node.interfaceTarget);
       }
@@ -988,6 +1114,8 @@
     if (_isUnreachable(node)) {
       return _makeUnreachableCall([node.value]);
     } else {
+      node.interfaceTarget = fieldMorpher
+          .adjustInstanceCallTarget(node.interfaceTarget, isSetter: true);
       if (node.interfaceTarget != null) {
         shaker.addUsedMember(node.interfaceTarget);
       }
@@ -1117,7 +1245,11 @@
     if (_isUnreachable(node)) {
       return _makeUnreachableCall([node.value]);
     } else {
-      assertx(shaker.isMemberBodyReachable(node.target), details: node.target);
+      final target = node.target;
+      assertx(shaker.isMemberBodyReachable(target), details: node);
+      if (target is Field && !shaker.retainField(target)) {
+        return node.value;
+      }
       return node;
     }
   }
@@ -1129,7 +1261,7 @@
       return _makeUnreachableCall(
           _flattenArguments(node.arguments, receiver: node.receiver));
     } else {
-      assertx(shaker.isMemberBodyReachable(node.target), details: node.target);
+      assertx(shaker.isMemberBodyReachable(node.target), details: node);
       return node;
     }
   }
@@ -1140,7 +1272,10 @@
     if (_isUnreachable(node)) {
       return _makeUnreachableCall([node.receiver]);
     } else {
-      assertx(shaker.isMemberBodyReachable(node.target), details: node.target);
+      final target = node.target;
+      assertx(shaker.isMemberBodyReachable(target), details: node);
+      assertx(target is! Field || shaker.isFieldGetterReachable(target),
+          details: node);
       return node;
     }
   }
@@ -1151,7 +1286,9 @@
     if (_isUnreachable(node)) {
       return _makeUnreachableCall([node.receiver, node.value]);
     } else {
-      assertx(shaker.isMemberBodyReachable(node.target), details: node.target);
+      assertx(shaker.isMemberBodyReachable(node.target), details: node);
+      node.target =
+          fieldMorpher.adjustInstanceCallTarget(node.target, isSetter: true);
       return node;
     }
   }
@@ -1203,12 +1340,20 @@
   }
 
   @override
-  visitFieldInitializer(FieldInitializer node) {
+  TreeNode visitFieldInitializer(FieldInitializer node) {
     node.transformChildren(this);
     if (_isUnreachable(node)) {
       return _makeUnreachableInitializer([node.value]);
     } else {
       assertx(shaker.isMemberBodyReachable(node.field), details: node.field);
+      if (!shaker.retainField(node.field)) {
+        if (mayHaveSideEffects(node.value)) {
+          return LocalInitializer(
+              VariableDeclaration(null, initializer: node.value));
+        } else {
+          return null;
+        }
+      }
       return node;
     }
   }
@@ -1587,3 +1732,26 @@
     constant.type.accept(typeVisitor);
   }
 }
+
+bool mayHaveSideEffects(Expression node) {
+  if (node is BasicLiteral ||
+      node is ConstantExpression ||
+      node is ThisExpression) {
+    return false;
+  }
+  if (node is VariableGet && !node.variable.isLate) {
+    return false;
+  }
+  if (node is StaticGet) {
+    final target = node.target;
+    if (target is Field && !target.isLate) {
+      final initializer = target.initializer;
+      if (initializer == null ||
+          initializer is BasicLiteral ||
+          initializer is ConstantExpression) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/bench_vector.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/bench_vector.dart.expect
index 9ae4ab1..e6599fe 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/bench_vector.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/bench_vector.dart.expect
@@ -15,7 +15,7 @@
 [@vm.procedure-attributes.metadata=getterCalledDynamically:false,hasTearOffUses:false,methodOrSetterSelectorId:1] [@vm.unboxing-info.metadata=(b)->d]  operator []([@vm.inferred-type.metadata=!] core::int* i) → core::double*
     return [@vm.direct-call.metadata=_Float64List::[]] [@vm.inferred-type.metadata=dart.core::_Double (skip check)] [@vm.direct-call.metadata=_Vector::_elements] [@vm.inferred-type.metadata=dart.typed_data::_Float64List] this.{self::_Vector::_elements}.{core::List::[]}([@vm.direct-call.metadata=_IntegerImplementation::+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}([@vm.direct-call.metadata=_Vector::_offset] [@vm.inferred-type.metadata=dart.core::_Smi (value: 0)] this.{self::_Vector::_offset}));
 [@vm.procedure-attributes.metadata=getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:2]  operator []=([@vm.inferred-type.metadata=dart.core::_OneByteString] core::int* i, core::double* value) → void {
-    let dynamic #t1 = [@vm.direct-call.metadata=_Vector::_elements] [@vm.inferred-type.metadata=dart.typed_data::_Float64List] this.{self::_Vector::_elements} in let dynamic #t2 = i in let dynamic #t3 = [@vm.direct-call.metadata=_Vector::_offset] [@vm.inferred-type.metadata=dart.core::_Smi (value: 0)] this.{self::_Vector::_offset} in throw "Attempt to execute code removed by Dart AOT compiler (TFA)";
+    let dynamic #t1 = [@vm.direct-call.metadata=_Vector::_elements] [@vm.inferred-type.metadata=dart.typed_data::_Float64List] this.{self::_Vector::_elements} in let dynamic #t2 = [@vm.direct-call.metadata=_Vector::_offset] [@vm.inferred-type.metadata=dart.core::_Smi (value: 0)] this.{self::_Vector::_offset} in throw "Attempt to execute code removed by Dart AOT compiler (TFA)";
   }
 [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3] [@vm.unboxing-info.metadata=(b)->d]  operator *([@vm.inferred-type.metadata=#lib::_Vector?] self::_Vector* a) → core::double* {
     core::double* result = 0.0;
@@ -27,7 +27,7 @@
 [@vm.inferred-type.metadata=#lib::_Vector?]static field self::_Vector* v = new self::_Vector::•(10);
 [@vm.inferred-type.metadata=dart.core::_Double?]static field core::double* x = 0.0;
 static method main(core::List<core::String*>* args) → dynamic {
-  core::Stopwatch* timer = let final core::Stopwatch* #t4 = new core::Stopwatch::•() in let final void #t5 = [@vm.direct-call.metadata=Stopwatch::start] [@vm.inferred-type.metadata=!? (skip check)] #t4.{core::Stopwatch::start}() in #t4;
+  core::Stopwatch* timer = let final core::Stopwatch* #t3 = new core::Stopwatch::•() in let final void #t4 = [@vm.direct-call.metadata=Stopwatch::start] [@vm.inferred-type.metadata=!? (skip check)] #t3.{core::Stopwatch::start}() in #t3;
   for (core::int* i = 0; [@vm.direct-call.metadata=_IntegerImplementation::<] [@vm.inferred-type.metadata=dart.core::bool (skip check)] i.{core::num::<}(100000000); i = [@vm.direct-call.metadata=_IntegerImplementation::+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}(1)) {
     self::x = [@vm.direct-call.metadata=_Double::+??] [@vm.inferred-type.metadata=dart.core::_Double (skip check)] [@vm.inferred-type.metadata=dart.core::_Double?] self::x.{core::double::+}([@vm.direct-call.metadata=_Vector::*??] [@vm.inferred-type.metadata=dart.core::_Double (skip check)] [@vm.inferred-type.metadata=#lib::_Vector?] self::v.{self::_Vector::*}([@vm.inferred-type.metadata=#lib::_Vector?] self::v));
   }
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/regress_41452_nnbd_strong.dart b/pkg/vm/testcases/transformations/type_flow/transformer/regress_41452_nnbd_strong.dart
index f2d7d4d..b60f24c5 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/regress_41452_nnbd_strong.dart
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/regress_41452_nnbd_strong.dart
@@ -8,8 +8,6 @@
 
 // @dart = 2.9
 
-import "package:expect/expect.dart";
-
 class _SplayTreeNode<Node extends _SplayTreeNode<Node>> {
   Node? left;
   _SplayTreeNode();
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/regress_41452_nnbd_strong.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/regress_41452_nnbd_strong.dart.expect
index 0285a9d..3128139 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/regress_41452_nnbd_strong.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/regress_41452_nnbd_strong.dart.expect
@@ -2,8 +2,6 @@
 import self as self;
 import "dart:core" as core;
 
-import "package:expect/expect.dart";
-
 abstract class _SplayTreeNode<Node extends self::_SplayTreeNode<self::_SplayTreeNode::Node> = self::_SplayTreeNode<dynamic>> extends core::Object {
 [@vm.inferred-type.metadata=dart.core::Null? (value: null)] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2]  generic-covariant-impl field self::_SplayTreeNode::Node? left = null;
   constructor •() → self::_SplayTreeNode<self::_SplayTreeNode::Node>
@@ -16,7 +14,6 @@
     ;
 }
 abstract class _SplayTree<Node extends self::_SplayTreeNode<self::_SplayTree::Node> = self::_SplayTreeNode<dynamic>> extends core::Object {
-[@vm.inferred-type.metadata=dart.core::Null? (value: null)] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:5,getterSelectorId:6]  generic-covariant-impl field self::_SplayTree::Node? _root = null;
   synthetic constructor •() → self::_SplayTree<self::_SplayTree::Node>
     : super core::Object::•()
     ;
@@ -26,9 +23,10 @@
       return;
     core::print([@vm.direct-call.metadata=_SplayTreeNode::left] [@vm.inferred-type.metadata=dart.core::Null? (value: null)] root{self::_SplayTree::Node}.{self::_SplayTreeNode::left});
   }
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:5]  abstract get /*isNullableByDefault*/ _root() → self::_SplayTree::Node?;
 }
 class SplayTreeMap<V extends core::Object? = dynamic> extends self::_SplayTree<self::_SplayTreeMapNode<self::SplayTreeMap::V%>> {
-[@vm.inferred-type.metadata=#lib::_SplayTreeMapNode] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:5,getterSelectorId:6]  generic-covariant-impl field self::_SplayTreeMapNode<self::SplayTreeMap::V%>? _root = new self::_SplayTreeMapNode::•<self::SplayTreeMap::V%>();
+[@vm.inferred-type.metadata=#lib::_SplayTreeMapNode] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:6,getterSelectorId:5]  generic-covariant-impl field self::_SplayTreeMapNode<self::SplayTreeMap::V%>? _root = new self::_SplayTreeMapNode::•<self::SplayTreeMap::V%>();
   synthetic constructor •() → self::SplayTreeMap<self::SplayTreeMap::V%>
     : super self::_SplayTree::•()
     ;
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/unreachable.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/unreachable.dart.expect
index 47a15c4..0ed46d3 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/unreachable.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/unreachable.dart.expect
@@ -14,7 +14,7 @@
 [@vm.inferred-type.metadata=#lib::B?]static field self::I* ii = new self::B::•();
 static method bar([@vm.inferred-type.metadata=#lib::B?] self::I* i) → void {
   if(i is self::A*) {
-    let dynamic #t1 = i{self::A*} in let dynamic #t2 = 42 in throw "Attempt to execute code removed by Dart AOT compiler (TFA)";
+    throw "Attempt to execute code removed by Dart AOT compiler (TFA)";
   }
 }
 static method main(core::List<core::String*>* args) → dynamic {
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field.dart.expect
index 079f6e9..3ba876c 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field.dart.expect
@@ -2,8 +2,6 @@
 import self as self;
 import "dart:core" as core;
 
-abstract class A extends core::Object {
-}
 class B extends core::Object {
   constructor •() → self::B*
     : super core::Object::•() {
@@ -16,8 +14,7 @@
     : super core::Object::•()
     ;
 }
-[@vm.inferred-type.metadata=dart.core::Null? (value: null)]static field self::A* field = throw "Attempt to execute code removed by Dart AOT compiler (TFA)";
 static method main() → void {
-  self::field = null;
+  null;
   [@vm.direct-call.metadata=C::instanceField] [@vm.inferred-type.metadata=!? (skip check)] new self::C::•().{self::C::instanceField} = null;
 }
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field2.dart b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field2.dart
new file mode 100644
index 0000000..af6a9b8
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field2.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Test for tree shaking of write-only fields.
+
+import "package:expect/expect.dart";
+
+foo() {}
+
+class A {
+  // Should be removed.
+  var unused1;
+
+  // Should be removed.
+  var unused2 = 42;
+
+  // Not removed due to a non-trivial initializer.
+  var unused3 = foo();
+}
+
+class B {
+  // Should be removed.
+  var unused4;
+
+  // Should be removed.
+  var unused5;
+
+  B(this.unused4) : unused5 = foo();
+}
+
+class C<T> {
+  // Should be replaced with setter.
+  T bar;
+}
+
+class D implements C<int> {
+  // Should be replaced with setter.
+  int bar;
+}
+
+class E {
+  // Should be replaced with getter.
+  final int bar;
+
+  E(this.bar);
+}
+
+class F implements E {
+  int get bar => 42;
+}
+
+class G {
+  // Not removed because used in a constant.
+  final int bazz;
+
+  const G(this.bazz);
+}
+
+class H {
+  // Should be replaced with setter.
+  int unused6;
+}
+
+class I extends H {
+  foo() {
+    super.unused6 = 3;
+  }
+}
+
+// Should be removed.
+int unusedStatic7 = foo();
+
+void main() {
+  new A();
+  new B('hi');
+
+  C<num> c = new D();
+  Expect.throws(() {
+    c.bar = 3.14;
+  });
+
+  E e = new F();
+  Expect.equals(42, e.bar);
+
+  Expect.isTrue(!identical(const G(1), const G(2)));
+
+  new I().foo();
+
+  unusedStatic7 = 5;
+}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field2.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field2.dart.expect
new file mode 100644
index 0000000..db8220b
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field2.dart.expect
@@ -0,0 +1,68 @@
+library #lib;
+import self as self;
+import "dart:core" as core;
+import "package:expect/expect.dart" as exp;
+
+import "package:expect/expect.dart";
+
+class A extends core::Object {
+[@vm.inferred-type.metadata=dart.core::Null? (value: null)] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2]  field dynamic unused3 = [@vm.inferred-type.metadata=dart.core::Null? (value: null)] self::foo();
+  synthetic constructor •() → self::A*
+    : super core::Object::•()
+    ;
+}
+class B extends core::Object {
+  constructor •([@vm.inferred-type.metadata=dart.core::_OneByteString (value: hi)] dynamic unused4) → self::B*
+    : dynamic #t1 = [@vm.inferred-type.metadata=dart.core::Null? (value: null)] self::foo(), super core::Object::•()
+    ;
+}
+abstract class C<T extends core::Object* = dynamic> extends core::Object {
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3]  abstract set bar(generic-covariant-impl self::C::T* value) → void;
+}
+class D extends core::Object implements self::C<core::int*> {
+  synthetic constructor •() → self::D*
+    : super core::Object::•()
+    ;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3]  set bar(generic-covariant-impl core::int* value) → void;
+}
+abstract class E extends core::Object {
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:4]  abstract get bar() → core::int*;
+}
+class F extends core::Object implements self::E {
+  synthetic constructor •() → self::F*
+    : super core::Object::•()
+    ;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:4]  get bar() → core::int*
+    return 42;
+}
+class G extends core::Object /*hasConstConstructor*/  {
+[@vm.inferred-type.metadata=dart.core::_Smi] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:5,getterSelectorId:6]  final field core::int* bazz;
+}
+abstract class H extends core::Object {
+  synthetic constructor •() → self::H*
+    : super core::Object::•()
+    ;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:7]  set unused6(core::int* value) → void;
+}
+class I extends self::H {
+  synthetic constructor •() → self::I*
+    : super self::H::•()
+    ;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:8,getterSelectorId:9]  method foo() → dynamic {
+    super.{self::H::unused6} = 3;
+  }
+}
+static method foo() → dynamic {}
+static method main() → void {
+  new self::A::•();
+  new self::B::•("hi");
+  self::C<core::num*>* c = new self::D::•();
+  exp::Expect::throws<dynamic>(() → core::Null? {
+    [@vm.call-site-attributes.metadata=receiverType:InterfaceType(C<num*>*)] [@vm.direct-call.metadata=D::bar] c.{self::C::bar} = 3.14;
+  });
+  self::E* e = new self::F::•();
+  exp::Expect::equals(42, [@vm.direct-call.metadata=F::bar] [@vm.inferred-type.metadata=dart.core::_Smi (value: 42)] e.{self::E::bar});
+  exp::Expect::isTrue(![@vm.inferred-type.metadata=dart.core::bool] core::identical(#C2, #C4));
+  [@vm.direct-call.metadata=I::foo] [@vm.inferred-type.metadata=!? (skip check)] new self::I::•().{self::I::foo}();
+  5;
+}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field3_nnbd.dart b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field3_nnbd.dart
new file mode 100644
index 0000000..bcc6ec4
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field3_nnbd.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Test for tree shaking of write-only late fields.
+// This test requires non-nullable experiment.
+
+// @dart = 2.9
+
+foo() {}
+
+class A {
+  // Should be replaced with setter.
+  late int x;
+
+  use() {
+    x = 3;
+  }
+}
+
+class B {
+  // Should be retained.
+  late final int x;
+
+  use() {
+    x = 3;
+  }
+}
+
+// Should be removed.
+late int staticLateA;
+
+// Should be retained.
+late final int staticLateB;
+
+void main() {
+  new A().use();
+  new B().use();
+
+  staticLateA = 4;
+  staticLateB = 4;
+}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field3_nnbd.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field3_nnbd.dart.expect
new file mode 100644
index 0000000..fd4556d
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field3_nnbd.dart.expect
@@ -0,0 +1,29 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    : super core::Object::•()
+    ;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2]  method use() → dynamic {
+    [@vm.direct-call.metadata=A::x] [@vm.inferred-type.metadata=!? (skip check)] this.{self::A::x} = 3;
+  }
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3]  set /*isNullableByDefault*/ x(core::int value) → void;
+}
+class B extends core::Object {
+[@vm.inferred-type.metadata=dart.core::_Smi?] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3,getterSelectorId:4]  late final [setter] field core::int x;
+  synthetic constructor •() → self::B
+    : super core::Object::•()
+    ;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2]  method use() → dynamic {
+    [@vm.direct-call.metadata=B::x] [@vm.inferred-type.metadata=!? (skip check)] this.{self::B::x} = 3;
+  }
+}
+[@vm.inferred-type.metadata=dart.core::_Smi?]late static final field core::int staticLateB;
+static method main() → void {
+  [@vm.direct-call.metadata=A::use] [@vm.inferred-type.metadata=!? (skip check)] new self::A::•().{self::A::use}();
+  [@vm.direct-call.metadata=B::use] [@vm.inferred-type.metadata=!? (skip check)] new self::B::•().{self::B::use}();
+  4;
+  self::staticLateB = 4;
+}
diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn
index 88b27a9..c7d66dc 100644
--- a/runtime/BUILD.gn
+++ b/runtime/BUILD.gn
@@ -236,6 +236,7 @@
     "vm:libdart_lib",
     "vm:libdart_vm",
   ]
+  compiler_lib = "vm:libdart_compiler"
   extra_configs = [ ":dart_shared_lib" ]
   include_dirs = [ "." ]
   public_configs = [ ":dart_public_config" ]
diff --git a/runtime/configs.gni b/runtime/configs.gni
index 04d8998..971d242 100644
--- a/runtime/configs.gni
+++ b/runtime/configs.gni
@@ -65,56 +65,67 @@
     suffix = "_jit"
     configs = _jit_config
     snapshot = true
+    compiler = true
   },
   {
     suffix = "_jit_product"
     configs = _jit_product_config
     snapshot = true
+    compiler = true
   },
   {
     suffix = "_precompiled_runtime"
     configs = _precompiled_runtime_config
     snapshot = true
+    compiler = false
   },
   {
     suffix = "_precompiled_runtime_product"
     configs = _precompiled_runtime_product_config
     snapshot = true
+    compiler = false
   },
   {
     suffix = "_precompiler"
     configs = _precompiler_config
     snapshot = false
+    compiler = true
   },
   {
     suffix = "_precompiler_product"
     configs = _precompiler_product_config
     snapshot = false
+    compiler = true
   },
   {
     suffix = "_precompiler_fuchsia"
     configs = _precompiler_fuchsia_config
     snapshot = false
+    compiler = true
   },
   {
     suffix = "_precompiler_product_fuchsia"
     configs = _precompiler_product_fuchsia_config
     snapshot = false
+    compiler = true
   },
   {
     suffix = "_precompiler_host_targeting_host"
     configs = _precompiler_host_targeting_host_config
     snapshot = false
+    compiler = true
   },
   {
     suffix = "_precompiler_product_host_targeting_host"
     configs = _precompiler_product_host_targeting_host_config
     snapshot = false
+    compiler = true
   },
   {
     suffix = "_libfuzzer"
     configs = _libfuzzer_config
     snapshot = true
+    compiler = true
   },
 ]
 
@@ -177,6 +188,13 @@
       foreach(dep, configurable_deps) {
         configured_deps += [ "${dep}${conf.suffix}" ]
       }
+      if (defined(compiler_lib)) {
+        if (conf.compiler) {
+          configured_deps += [ "${compiler_lib}${conf.suffix}" ]
+        } else {
+          not_needed([ "compiler_lib" ])
+        }
+      }
       deps = configured_deps + extra_deps
       if (conf.snapshot) {
         if (defined(snapshot_sources)) {
@@ -190,3 +208,47 @@
     }
   }
 }
+
+template("library_for_all_configs_with_compiler") {
+  assert(defined(invoker.target_type))
+  extra_configs = []
+  if (defined(invoker.extra_configs)) {
+    extra_configs += invoker.extra_configs
+  }
+  configurable_deps = []
+  if (defined(invoker.configurable_deps)) {
+    configurable_deps += invoker.configurable_deps
+  }
+  extra_deps = []
+  if (defined(invoker.extra_deps)) {
+    extra_deps += invoker.extra_deps
+  }
+  foreach(conf, _all_configs) {
+    if (conf.compiler) {
+      target(invoker.target_type, "${target_name}${conf.suffix}") {
+        forward_variables_from(invoker,
+                               "*",
+                               [
+                                 "extra_configs",
+                                 "extra_deps",
+                                 "configurable_deps",
+                               ])
+        configs += conf.configs + extra_configs
+        configured_deps = []
+        foreach(dep, configurable_deps) {
+          configured_deps += [ "${dep}${conf.suffix}" ]
+        }
+        deps = configured_deps + extra_deps
+        if (conf.snapshot) {
+          if (defined(snapshot_sources)) {
+            sources += snapshot_sources
+          }
+        } else {
+          if (defined(snapshot_sources)) {
+            not_needed([ "snapshot_sources" ])
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/runtime/lib/ffi.cc b/runtime/lib/ffi.cc
index 47ead67..58f1d77 100644
--- a/runtime/lib/ffi.cc
+++ b/runtime/lib/ffi.cc
@@ -8,11 +8,7 @@
 #include "vm/bootstrap_natives.h"
 #include "vm/class_finalizer.h"
 #include "vm/class_id.h"
-#include "vm/compiler/assembler/assembler.h"
-#include "vm/compiler/ffi/call.h"
-#include "vm/compiler/ffi/callback.h"
 #include "vm/compiler/ffi/native_type.h"
-#include "vm/compiler/jit/compiler.h"
 #include "vm/exceptions.h"
 #include "vm/flags.h"
 #include "vm/log.h"
@@ -22,6 +18,13 @@
 #include "vm/object_store.h"
 #include "vm/symbols.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/assembler/assembler.h"
+#include "vm/compiler/ffi/call.h"
+#include "vm/compiler/ffi/callback.h"
+#include "vm/compiler/jit/compiler.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 // The following functions are runtime checks on type arguments.
@@ -69,7 +72,7 @@
   return Double::Cast(instance);
 }
 
-// Calcuate the size of a native type.
+// Calculate the size of a native type.
 //
 // You must check [IsConcreteNativeType] and [CheckSized] first to verify that
 // this type has a defined size.
diff --git a/runtime/lib/isolate.cc b/runtime/lib/isolate.cc
index 465c84f..1855954 100644
--- a/runtime/lib/isolate.cc
+++ b/runtime/lib/isolate.cc
@@ -167,7 +167,11 @@
         return;
       }
 
+#if defined(DART_PRECOMPILED_RUNTIME)
+      isolate = CreateWithinExistingIsolateGroupAOT(group, name, &error);
+#else
       isolate = CreateWithinExistingIsolateGroup(group, name, &error);
+#endif
       parent_isolate_->DecrementSpawnCount();
       parent_isolate_ = nullptr;
       if (isolate == nullptr) {
diff --git a/runtime/lib/regexp.cc b/runtime/lib/regexp.cc
index eb4237c..6deab66 100644
--- a/runtime/lib/regexp.cc
+++ b/runtime/lib/regexp.cc
@@ -8,10 +8,13 @@
 #include "vm/native_entry.h"
 #include "vm/object.h"
 #include "vm/regexp_assembler_bytecode.h"
-#include "vm/regexp_assembler_ir.h"
 #include "vm/regexp_parser.h"
 #include "vm/thread.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/regexp_assembler_ir.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 DEFINE_NATIVE_ENTRY(RegExp_factory, 0, 6) {
diff --git a/runtime/observatory/tests/service/contexts_test.dart b/runtime/observatory/tests/service/contexts_test.dart
index ad17f38..33021a1 100644
--- a/runtime/observatory/tests/service/contexts_test.dart
+++ b/runtime/observatory/tests/service/contexts_test.dart
@@ -8,7 +8,15 @@
 import 'package:test/test.dart';
 import 'test_helper.dart';
 
-var cleanBlock, copyingBlock, fullBlock, fullBlockWithChain;
+// Make sure these variables are not removed by the tree shaker.
+@pragma("vm:entry-point")
+var cleanBlock;
+@pragma("vm:entry-point")
+var copyingBlock;
+@pragma("vm:entry-point")
+var fullBlock;
+@pragma("vm:entry-point")
+var fullBlockWithChain;
 
 Function genCleanBlock() {
   block(x) => x;
diff --git a/runtime/observatory/tests/service/dominator_tree_vm_test.dart b/runtime/observatory/tests/service/dominator_tree_vm_test.dart
index ebb9c77..3fb617b 100644
--- a/runtime/observatory/tests/service/dominator_tree_vm_test.dart
+++ b/runtime/observatory/tests/service/dominator_tree_vm_test.dart
@@ -12,62 +12,86 @@
 
 // small example from [Lenguaer & Tarjan 1979]
 class R {
+  // All fields are marked with @pragma("vm:entry-point")
+  // in order to make sure they are not removed by the tree shaker
+  // even though they are never read.
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
+  @pragma("vm:entry-point")
   var z;
 }
 
 class A {
+  @pragma("vm:entry-point")
   var x;
 }
 
 class B {
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
+  @pragma("vm:entry-point")
   var z;
 }
 
 class C {
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
 }
 
 class D {
+  @pragma("vm:entry-point")
   var x;
 }
 
 class E {
+  @pragma("vm:entry-point")
   var x;
 }
 
 class F {
+  @pragma("vm:entry-point")
   var x;
 }
 
 class G {
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
 }
 
 class H {
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
 }
 
 class I {
+  @pragma("vm:entry-point")
   var x;
 }
 
 class J {
+  @pragma("vm:entry-point")
   var x;
 }
 
 class K {
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
 }
 
 class L {
+  @pragma("vm:entry-point")
   var x;
 }
 
diff --git a/runtime/observatory/tests/service/dominator_tree_vm_with_double_field_test.dart b/runtime/observatory/tests/service/dominator_tree_vm_with_double_field_test.dart
index 858f10a..eee07b2 100644
--- a/runtime/observatory/tests/service/dominator_tree_vm_with_double_field_test.dart
+++ b/runtime/observatory/tests/service/dominator_tree_vm_with_double_field_test.dart
@@ -22,62 +22,86 @@
 // small example from [Lenguaer & Tarjan 1979]
 class R {
   final double fld = getDoubleWithHeapObjectTag();
+  // Fields are marked with @pragma("vm:entry-point")
+  // in order to make sure they are not removed by the tree shaker
+  // even though they are never read.
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
+  @pragma("vm:entry-point")
   var z;
 }
 
 class A {
+  @pragma("vm:entry-point")
   var x;
 }
 
 class B {
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
+  @pragma("vm:entry-point")
   var z;
 }
 
 class C {
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
 }
 
 class D {
+  @pragma("vm:entry-point")
   var x;
 }
 
 class E {
+  @pragma("vm:entry-point")
   var x;
 }
 
 class F {
+  @pragma("vm:entry-point")
   var x;
 }
 
 class G {
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
 }
 
 class H {
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
 }
 
 class I {
+  @pragma("vm:entry-point")
   var x;
 }
 
 class J {
+  @pragma("vm:entry-point")
   var x;
 }
 
 class K {
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
 }
 
 class L {
+  @pragma("vm:entry-point")
   var x;
 }
 
diff --git a/runtime/observatory/tests/service/get_instances_rpc_test.dart b/runtime/observatory/tests/service/get_instances_rpc_test.dart
index 0f234be..696f521 100644
--- a/runtime/observatory/tests/service/get_instances_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_instances_rpc_test.dart
@@ -9,7 +9,10 @@
 
 class _TestClass {
   _TestClass(this.x, this.y);
+  // Make sure these fields are not removed by the tree shaker.
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
 }
 
diff --git a/runtime/observatory/tests/service/get_retained_size_rpc_test.dart b/runtime/observatory/tests/service/get_retained_size_rpc_test.dart
index 7060f13..3241ce5 100644
--- a/runtime/observatory/tests/service/get_retained_size_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_retained_size_rpc_test.dart
@@ -9,10 +9,14 @@
 
 class _TestClass {
   _TestClass(this.x, this.y);
+  // Make sure these fields are not removed by the tree shaker.
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
 }
 
+@pragma("vm:entry-point")
 var myVar;
 
 @pragma("vm:entry-point")
diff --git a/runtime/observatory/tests/service/get_retaining_path_rpc_test.dart b/runtime/observatory/tests/service/get_retaining_path_rpc_test.dart
index 78cacc0..1a036e5 100644
--- a/runtime/observatory/tests/service/get_retaining_path_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_retaining_path_rpc_test.dart
@@ -9,7 +9,10 @@
 
 class _TestClass {
   _TestClass();
+  // Make sure these fields are not removed by the tree shaker.
+  @pragma("vm:entry-point")
   var x;
+  @pragma("vm:entry-point")
   var y;
 }
 
@@ -193,7 +196,7 @@
       'limit': 100,
     };
     var result = await isolate.invokeRpcNoUpgrade('getRetainingPath', params);
-    expect(result['gcRootType'], 'object store');
+    expect(result['gcRootType'], 'isolate_object store');
     expect(result['elements'].length, 0);
   },
 ];
diff --git a/runtime/observatory/tests/service/inbound_references_test.dart b/runtime/observatory/tests/service/inbound_references_test.dart
index 4c1e496..267f1b1 100644
--- a/runtime/observatory/tests/service/inbound_references_test.dart
+++ b/runtime/observatory/tests/service/inbound_references_test.dart
@@ -9,6 +9,8 @@
 import 'test_helper.dart';
 
 class Node {
+  // Make sure this field is not removed by the tree shaker.
+  @pragma("vm:entry-point")
   var edge;
 }
 
diff --git a/runtime/observatory/tests/service/instance_field_order_rpc_test.dart b/runtime/observatory/tests/service/instance_field_order_rpc_test.dart
index 9ff16e4..b1a3c38 100644
--- a/runtime/observatory/tests/service/instance_field_order_rpc_test.dart
+++ b/runtime/observatory/tests/service/instance_field_order_rpc_test.dart
@@ -9,12 +9,17 @@
 import 'test_helper.dart';
 
 class Super {
+  // Make sure these fields are not removed by the tree shaker.
+  @pragma("vm:entry-point")
   var z = 1;
+  @pragma("vm:entry-point")
   var y = 2;
 }
 
 class Sub extends Super {
+  @pragma("vm:entry-point")
   var y = 3;
+  @pragma("vm:entry-point")
   var x = 4;
 }
 
diff --git a/runtime/observatory/tests/service/object_graph_vm_test.dart b/runtime/observatory/tests/service/object_graph_vm_test.dart
index 852c823..9414709 100644
--- a/runtime/observatory/tests/service/object_graph_vm_test.dart
+++ b/runtime/observatory/tests/service/object_graph_vm_test.dart
@@ -9,7 +9,10 @@
 import 'test_helper.dart';
 
 class Foo {
+  // Make sure these fields are not removed by the tree shaker.
+  @pragma("vm:entry-point")
   dynamic left;
+  @pragma("vm:entry-point")
   dynamic right;
 }
 
diff --git a/runtime/observatory/tests/service/reachable_size_test.dart b/runtime/observatory/tests/service/reachable_size_test.dart
index 6bc92f5..bca88d8 100644
--- a/runtime/observatory/tests/service/reachable_size_test.dart
+++ b/runtime/observatory/tests/service/reachable_size_test.dart
@@ -9,7 +9,11 @@
 import 'service_test_common.dart';
 
 class Pair {
-  var x, y;
+  // Make sure these fields are not removed by the tree shaker.
+  @pragma("vm:entry-point")
+  var x;
+  @pragma("vm:entry-point")
+  var y;
 }
 
 var p1;
diff --git a/runtime/observatory/tests/service/regexp_function_test.dart b/runtime/observatory/tests/service/regexp_function_test.dart
index a44cb5b..d11abe0 100644
--- a/runtime/observatory/tests/service/regexp_function_test.dart
+++ b/runtime/observatory/tests/service/regexp_function_test.dart
@@ -9,7 +9,10 @@
 import 'package:test/test.dart';
 import 'test_helper.dart';
 
+// Make sure these variables are not removed by the tree shaker.
+@pragma("vm:entry-point")
 var regex0;
+@pragma("vm:entry-point")
 var regex;
 
 void script() {
diff --git a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart
index 5f260b4..00c7f4f 100644
--- a/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart
+++ b/runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart
@@ -54,15 +54,21 @@
     // Run the AOT compiler with/without Dwarf stack traces.
     final scriptDwarfSnapshot = path.join(tempDir, 'dwarf.so');
     final scriptNonDwarfSnapshot = path.join(tempDir, 'non_dwarf.so');
+    final scriptDwarfDebugInfo = path.join(tempDir, 'debug_info.so');
     await Future.wait(<Future>[
       run(genSnapshot, <String>[
-        '--dwarf-stack-traces',
+        // We test --dwarf-stack-traces-mode, not --dwarf-stack-traces, because
+        // the latter is a handler that sets the former and also may change
+        // other flags. This way, we limit the difference between the two
+        // snapshots and also directly test the flag saved as a VM global flag.
+        '--dwarf-stack-traces-mode',
+        '--save-debugging-info=$scriptDwarfDebugInfo',
         '--snapshot-kind=app-aot-elf',
         '--elf=$scriptDwarfSnapshot',
         scriptDill,
       ]),
       run(genSnapshot, <String>[
-        '--no-dwarf-stack-traces',
+        '--no-dwarf-stack-traces-mode',
         '--snapshot-kind=app-aot-elf',
         '--elf=$scriptNonDwarfSnapshot',
         scriptDill,
@@ -71,24 +77,24 @@
 
     // Run the resulting Dwarf-AOT compiled script.
     final dwarfTrace1 = await runError(aotRuntime, <String>[
-      '--dwarf-stack-traces',
+      '--dwarf-stack-traces-mode',
       scriptDwarfSnapshot,
       scriptDill,
     ]);
     final dwarfTrace2 = await runError(aotRuntime, <String>[
-      '--no-dwarf-stack-traces',
+      '--no-dwarf-stack-traces-mode',
       scriptDwarfSnapshot,
       scriptDill,
     ]);
 
     // Run the resulting non-Dwarf-AOT compiled script.
     final nonDwarfTrace1 = await runError(aotRuntime, <String>[
-      '--dwarf-stack-traces',
+      '--dwarf-stack-traces-mode',
       scriptNonDwarfSnapshot,
       scriptDill,
     ]);
     final nonDwarfTrace2 = await runError(aotRuntime, <String>[
-      '--no-dwarf-stack-traces',
+      '--no-dwarf-stack-traces-mode',
       scriptNonDwarfSnapshot,
       scriptDill,
     ]);
@@ -105,9 +111,7 @@
 
     // Check that translating the DWARF stack trace (without internal frames)
     // matches the symbolic stack trace.
-    final dwarf = Dwarf.fromFile(scriptDwarfSnapshot);
-    // We are generating unstripped snapshots, so the snapshot should include
-    // the appropriate DWARF information.
+    final dwarf = Dwarf.fromFile(scriptDwarfDebugInfo);
     assert(dwarf != null);
     final translatedDwarfTrace1 = await Stream.fromIterable(dwarfTrace1)
         .transform(DwarfStackTraceDecoder(dwarf))
diff --git a/runtime/vm/BUILD.gn b/runtime/vm/BUILD.gn
index bac066b..e3b2a7a 100644
--- a/runtime/vm/BUILD.gn
+++ b/runtime/vm/BUILD.gn
@@ -90,11 +90,23 @@
                                   "*_test.cc",
                                   "*_test.h",
                                 ])
-  sources = vm_sources + rebase_path(compiler_sources, ".", "./compiler/") +
+  sources = vm_sources + rebase_path(compiler_api_sources, ".", "./compiler/") +
+            rebase_path(disassembler_sources, ".", "./compiler/") +
             rebase_path(heap_sources, ".", "./heap/")
   include_dirs = [ ".." ]
 }
 
+library_for_all_configs_with_compiler("libdart_compiler") {
+  target_type = "source_set"
+  public_configs = [ ":libdart_vm_config" ]
+  set_sources_assignment_filter([
+                                  "*_test.cc",
+                                  "*_test.h",
+                                ])
+  sources = rebase_path(compiler_sources, ".", "./compiler/")
+  include_dirs = [ ".." ]
+}
+
 library_for_all_configs("libdart_lib") {
   target_type = "source_set"
   if (is_fuchsia) {
diff --git a/runtime/vm/class_finalizer.cc b/runtime/vm/class_finalizer.cc
index cefcf0b..a53cf86 100644
--- a/runtime/vm/class_finalizer.cc
+++ b/runtime/vm/class_finalizer.cc
@@ -1519,7 +1519,7 @@
     // The [HeapIterationScope] also safepoints all threads.
     HeapIterationScope his(T);
 
-    IG->class_table()->Remap(old_to_new_cid);
+    IG->shared_class_table()->Remap(old_to_new_cid);
     IG->ForEachIsolate(
         [&](Isolate* I) {
           I->set_remapping_cids(true);
diff --git a/runtime/vm/class_table.cc b/runtime/vm/class_table.cc
index c08e1c6..338cc0a 100644
--- a/runtime/vm/class_table.cc
+++ b/runtime/vm/class_table.cc
@@ -31,7 +31,8 @@
         calloc(capacity_, sizeof(RelaxedAtomic<intptr_t>))));
   } else {
     // Duplicate the class table from the VM isolate.
-    auto vm_shared_class_table = Dart::vm_isolate()->group()->class_table();
+    auto vm_shared_class_table =
+        Dart::vm_isolate()->group()->shared_class_table();
     capacity_ = vm_shared_class_table->capacity_;
     // Note that [calloc] will zero-initialize the memory.
     RelaxedAtomic<intptr_t>* table = reinterpret_cast<RelaxedAtomic<intptr_t>*>(
@@ -71,6 +72,13 @@
   NOT_IN_PRODUCT(free(trace_allocation_table_.load()));
 }
 
+void ClassTable::set_table(RawClass** table) {
+  Isolate* isolate = Isolate::Current();
+  ASSERT(isolate != nullptr);
+  table_.store(table);
+  isolate->set_cached_class_table_table(table);
+}
+
 ClassTable::ClassTable(SharedClassTable* shared_class_table)
     : top_(kNumPredefinedCids),
       capacity_(0),
@@ -81,6 +89,9 @@
     ASSERT(kInitialCapacity >= kNumPredefinedCids);
     capacity_ = kInitialCapacity;
     // Note that [calloc] will zero-initialize the memory.
+    // Don't use set_table because caller is supposed to set up isolates
+    // cached copy when constructing ClassTable. Isolate::Current might not
+    // be available at this point yet.
     table_.store(static_cast<RawClass**>(calloc(capacity_, sizeof(RawClass*))));
   } else {
     // Duplicate the class table from the VM isolate.
@@ -100,6 +111,9 @@
     table[kDynamicCid] = vm_class_table->At(kDynamicCid);
     table[kVoidCid] = vm_class_table->At(kVoidCid);
     table[kNeverCid] = vm_class_table->At(kNeverCid);
+    // Don't use set_table because caller is supposed to set up isolates
+    // cached copy when constructing ClassTable. Isolate::Current might not
+    // be available at this point yet.
     table_.store(table);
   }
 }
@@ -226,7 +240,7 @@
     new_table[i] = 0;
   }
   old_class_tables_->Add(old_table);
-  table_.store(new_table);
+  set_table(new_table);
 
   capacity_ = new_capacity;
 }
diff --git a/runtime/vm/class_table.h b/runtime/vm/class_table.h
index a21dd51..510927a 100644
--- a/runtime/vm/class_table.h
+++ b/runtime/vm/class_table.h
@@ -355,14 +355,6 @@
 
   void Print();
 
-  // Used by the generated code.
-  static intptr_t table_offset() { return OFFSET_OF(ClassTable, table_); }
-
-  // Used by the generated code.
-  static intptr_t shared_class_table_offset() {
-    return OFFSET_OF(ClassTable, shared_class_table_);
-  }
-
 #ifndef PRODUCT
   // Describes layout of heap stats for code generation. See offset_extractor.cc
   struct ArrayLayout {
@@ -387,9 +379,11 @@
   friend class MarkingWeakVisitor;
   friend class Scavenger;
   friend class ScavengerWeakVisitor;
+  friend class Dart;
   friend Isolate* CreateWithinExistingIsolateGroup(IsolateGroup* group,
                                                    const char* name,
                                                    char** error);
+  friend class Isolate;  // for table()
   static const int kInitialCapacity = SharedClassTable::kInitialCapacity;
   static const int kCapacityIncrement = SharedClassTable::kCapacityIncrement;
 
@@ -397,6 +391,9 @@
 
   void Grow(intptr_t index);
 
+  RawClass** table() { return table_.load(); }
+  void set_table(RawClass** table);
+
   intptr_t top_;
   intptr_t capacity_;
 
diff --git a/runtime/vm/clustered_snapshot.cc b/runtime/vm/clustered_snapshot.cc
index 25a37ad..b498c29 100644
--- a/runtime/vm/clustered_snapshot.cc
+++ b/runtime/vm/clustered_snapshot.cc
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+#include <memory>
+
 #include "vm/clustered_snapshot.h"
 
 #include "platform/assert.h"
@@ -9,10 +11,8 @@
 #include "vm/bss_relocs.h"
 #include "vm/class_id.h"
 #include "vm/code_observers.h"
+#include "vm/compiler/api/print_filter.h"
 #include "vm/compiler/assembler/disassembler.h"
-#include "vm/compiler/backend/code_statistics.h"
-#include "vm/compiler/backend/il_printer.h"
-#include "vm/compiler/relocation.h"
 #include "vm/dart.h"
 #include "vm/dispatch_table.h"
 #include "vm/flag_list.h"
@@ -28,6 +28,12 @@
 #include "vm/timeline.h"
 #include "vm/version.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/backend/code_statistics.h"
+#include "vm/compiler/backend/il_printer.h"
+#include "vm/compiler/relocation.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
@@ -231,7 +237,8 @@
   UnboxedFieldBitmap CalculateTargetUnboxedFieldsBitmap(Serializer* s,
                                                         intptr_t class_id) {
     const auto unboxed_fields_bitmap_host =
-        s->isolate()->group()->class_table()->GetUnboxedFieldsMapAt(class_id);
+        s->isolate()->group()->shared_class_table()->GetUnboxedFieldsMapAt(
+            class_id);
 
     UnboxedFieldBitmap unboxed_fields_bitmap;
     if (unboxed_fields_bitmap_host.IsEmpty() ||
@@ -349,7 +356,7 @@
       }
     }
 
-    auto shared_class_table = d->isolate()->group()->class_table();
+    auto shared_class_table = d->isolate()->group()->shared_class_table();
     for (intptr_t id = start_index_; id < stop_index_; id++) {
       RawClass* cls = reinterpret_cast<RawClass*>(d->Ref(id));
       Deserializer::InitializeHeader(cls, kClassCid, Class::InstanceSize());
@@ -1201,6 +1208,10 @@
         field.InitializeGuardedListLengthInObjectOffset();
       }
     }
+
+    Isolate* isolate = Isolate::Current();
+    isolate->set_saved_initial_field_table(
+        std::shared_ptr<FieldTable>(isolate->field_table()->Clone()));
   }
 };
 
@@ -1511,13 +1522,30 @@
     s->Push(code->ptr()->pc_descriptors_);
     s->Push(code->ptr()->catch_entry_);
     s->Push(code->ptr()->compressed_stackmaps_);
-    if (!FLAG_dwarf_stack_traces) {
+    if (!FLAG_dwarf_stack_traces_mode) {
       s->Push(code->ptr()->inlined_id_to_function_);
       s->Push(code->ptr()->code_source_map_);
     }
     if (s->kind() == Snapshot::kFullJIT) {
       s->Push(code->ptr()->deopt_info_array_);
       s->Push(code->ptr()->static_calls_target_table_);
+    } else if (s->kind() == Snapshot::kFullAOT) {
+#if defined(DART_PRECOMPILER)
+      auto const calls_array = code->ptr()->static_calls_target_table_;
+      if (calls_array != Array::null()) {
+        // Some Code entries in the static calls target table may only be
+        // accessible via here, so push the Code objects.
+        auto const length = Smi::Value(calls_array->ptr()->length_);
+        for (intptr_t i = 0; i < length; i++) {
+          auto const object = calls_array->ptr()->data()[i];
+          if (object->IsHeapObject() && object->IsCode()) {
+            s->Push(object);
+          }
+        }
+      }
+#else
+      UNREACHABLE();
+#endif
     }
 #if !defined(PRODUCT)
     s->Push(code->ptr()->return_address_metadata_);
@@ -1591,7 +1619,7 @@
       WriteField(code, pc_descriptors_);
       WriteField(code, catch_entry_);
       WriteField(code, compressed_stackmaps_);
-      if (FLAG_dwarf_stack_traces) {
+      if (FLAG_dwarf_stack_traces_mode) {
         WriteFieldValue(inlined_id_to_function_, Array::null());
         WriteFieldValue(code_source_map_, CodeSourceMap::null());
       } else {
@@ -1729,7 +1757,7 @@
       if (owner.IsFunction()) {
         if ((FLAG_disassemble ||
              (code.is_optimized() && FLAG_disassemble_optimized)) &&
-            FlowGraphPrinter::ShouldPrint(Function::Cast(owner))) {
+            compiler::PrintFilter::ShouldPrint(Function::Cast(owner))) {
           Disassembler::DisassembleCode(Function::Cast(owner), code,
                                         code.is_optimized());
         }
@@ -3022,7 +3050,8 @@
     const intptr_t next_field_offset = host_next_field_offset_in_words_
                                        << kWordSizeLog2;
     const auto unboxed_fields_bitmap =
-        s->isolate()->group()->class_table()->GetUnboxedFieldsMapAt(cid_);
+        s->isolate()->group()->shared_class_table()->GetUnboxedFieldsMapAt(
+            cid_);
     intptr_t offset = Instance::NextFieldOffset();
     while (offset < next_field_offset) {
       // Skips unboxed fields
@@ -3058,7 +3087,8 @@
                                  << kWordSizeLog2;
     const intptr_t count = objects_.length();
     const auto unboxed_fields_bitmap =
-        s->isolate()->group()->class_table()->GetUnboxedFieldsMapAt(cid_);
+        s->isolate()->group()->shared_class_table()->GetUnboxedFieldsMapAt(
+            cid_);
     for (intptr_t i = 0; i < count; i++) {
       RawInstance* instance = objects_[i];
       AutoTraceObject(instance);
@@ -3116,7 +3146,8 @@
         Object::RoundedAllocationSize(instance_size_in_words_ * kWordSize);
 
     const auto unboxed_fields_bitmap =
-        d->isolate()->group()->class_table()->GetUnboxedFieldsMapAt(cid_);
+        d->isolate()->group()->shared_class_table()->GetUnboxedFieldsMapAt(
+            cid_);
     for (intptr_t id = start_index_; id < stop_index_; id++) {
       RawInstance* instance = reinterpret_cast<RawInstance*>(d->Ref(id));
       bool is_canonical = d->Read<bool>();
@@ -5521,7 +5552,7 @@
 #undef DECLARE_OBJECT_STORE_FIELD
 };
 
-void Serializer::WriteIsolateSnapshot(intptr_t num_base_objects,
+void Serializer::WriteProgramSnapshot(intptr_t num_base_objects,
                                       ObjectStore* object_store) {
   NoSafepointScope no_safepoint;
 
@@ -5532,7 +5563,7 @@
       AddBaseObject(base_objects.At(i));
     }
   } else {
-    // Base objects carried over from WriteVMIsolateSnapshot.
+    // Base objects carried over from WriteVMSnapshot.
     num_base_objects_ += num_base_objects;
     next_ref_index_ += num_base_objects;
   }
@@ -5813,7 +5844,7 @@
   }
   ASSERT(repeat_count == 0);
 
-  I->set_dispatch_table(table);
+  I->group()->set_dispatch_table(table);
 #endif
 }
 
@@ -6196,7 +6227,7 @@
   }
 }
 
-void Deserializer::ReadIsolateSnapshot(ObjectStore* object_store) {
+void Deserializer::ReadProgramSnapshot(ObjectStore* object_store) {
   Array& refs = Array::Handle();
   Prepare();
 
@@ -6234,8 +6265,8 @@
   thread()->isolate()->class_table()->CopySizesFromClassObjects();
   heap_->old_space()->EvaluateAfterLoading();
 
-#if defined(DEBUG)
   Isolate* isolate = thread()->isolate();
+#if defined(DEBUG)
   isolate->ValidateClassTable();
   isolate->heap()->Verify();
 #endif
@@ -6243,13 +6274,12 @@
   for (intptr_t i = 0; i < num_clusters_; i++) {
     clusters_[i]->PostLoad(refs, kind_, zone_);
   }
-  object_store->PostLoad();
+  isolate->isolate_object_store()->PreallocateObjects();
 
   // Setup native resolver for bootstrap impl.
   Bootstrap::SetupNativeResolver();
 }
 
-
 #if !defined(DART_PRECOMPILED_RUNTIME)
 FullSnapshotWriter::FullSnapshotWriter(Snapshot::Kind kind,
                                        uint8_t** vm_snapshot_data_buffer,
@@ -6324,8 +6354,8 @@
   return num_objects;
 }
 
-void FullSnapshotWriter::WriteIsolateSnapshot(intptr_t num_base_objects) {
-  TIMELINE_DURATION(thread(), Isolate, "WriteIsolateSnapshot");
+void FullSnapshotWriter::WriteProgramSnapshot(intptr_t num_base_objects) {
+  TIMELINE_DURATION(thread(), Isolate, "WriteProgramSnapshot");
 
   Serializer serializer(thread(), kind_, isolate_snapshot_data_buffer_, alloc_,
                         kInitialSize, isolate_image_writer_, /*vm=*/false,
@@ -6344,7 +6374,7 @@
   serializer.WriteVersionAndFeatures(false);
   // Isolate snapshot roots are:
   // - the object store
-  serializer.WriteIsolateSnapshot(num_base_objects, object_store);
+  serializer.WriteProgramSnapshot(num_base_objects, object_store);
   serializer.FillHeader(serializer.kind());
   clustered_isolate_size_ = serializer.bytes_written();
 
@@ -6375,7 +6405,7 @@
   }
 
   if (isolate_snapshot_data_buffer() != NULL) {
-    WriteIsolateSnapshot(num_base_objects);
+    WriteProgramSnapshot(num_base_objects);
   }
 
   if (FLAG_print_snapshot_sizes) {
@@ -6538,7 +6568,7 @@
   return ApiError::null();
 }
 
-RawApiError* FullSnapshotReader::ReadIsolateSnapshot() {
+RawApiError* FullSnapshotReader::ReadProgramSnapshot() {
   SnapshotHeaderReader header_reader(kind_, buffer_, size_);
   intptr_t offset = 0;
   char* error =
@@ -6564,7 +6594,7 @@
   }
 
   auto object_store = thread_->isolate()->object_store();
-  deserializer.ReadIsolateSnapshot(object_store);
+  deserializer.ReadProgramSnapshot(object_store);
 
 #if defined(DART_PRECOMPILED_RUNTIME)
   if (FLAG_use_bare_instructions) {
diff --git a/runtime/vm/clustered_snapshot.h b/runtime/vm/clustered_snapshot.h
index 791c1cf..b7da466 100644
--- a/runtime/vm/clustered_snapshot.h
+++ b/runtime/vm/clustered_snapshot.h
@@ -157,7 +157,7 @@
   }
 
   intptr_t WriteVMSnapshot(const Array& symbols);
-  void WriteIsolateSnapshot(intptr_t num_base_objects,
+  void WriteProgramSnapshot(intptr_t num_base_objects,
                             ObjectStore* object_store);
 
   void AddVMIsolateBaseObjects();
@@ -539,7 +539,7 @@
   // message otherwise.
   RawApiError* VerifyImageAlignment();
 
-  void ReadIsolateSnapshot(ObjectStore* object_store);
+  void ReadProgramSnapshot(ObjectStore* object_store);
   void ReadVMSnapshot();
 
   void AddVMIsolateBaseObjects();
@@ -682,7 +682,7 @@
   Isolate* isolate() const { return thread_->isolate(); }
   Heap* heap() const { return isolate()->heap(); }
 
-  // Writes a full snapshot of the Isolate.
+  // Writes a full snapshot of the program(VM isolate, regular isolate group).
   void WriteFullSnapshot();
 
   intptr_t VmIsolateSnapshotSize() const { return vm_isolate_snapshot_size_; }
@@ -692,8 +692,8 @@
   // Writes a snapshot of the VM Isolate.
   intptr_t WriteVMSnapshot();
 
-  // Writes a full snapshot of a regular Dart Isolate.
-  void WriteIsolateSnapshot(intptr_t num_base_objects);
+  // Writes a full snapshot of regular Dart isolate group.
+  void WriteProgramSnapshot(intptr_t num_base_objects);
 
   Thread* thread_;
   Snapshot::Kind kind_;
@@ -724,7 +724,7 @@
   ~FullSnapshotReader() {}
 
   RawApiError* ReadVMSnapshot();
-  RawApiError* ReadIsolateSnapshot();
+  RawApiError* ReadProgramSnapshot();
 
  private:
   RawApiError* ConvertToApiError(char* message);
diff --git a/runtime/vm/code_comments.cc b/runtime/vm/code_comments.cc
index 74f8458..d1344d3 100644
--- a/runtime/vm/code_comments.cc
+++ b/runtime/vm/code_comments.cc
@@ -1,14 +1,13 @@
 // Copyright (c) 2019, 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.
+#if !defined(DART_PRECOMPILED_RUNTIME) &&                                      \
+    (!defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER))
 
 #include "vm/code_comments.h"
 
 namespace dart {
 
-#if !defined(DART_PRECOMPILED_RUNTIME) &&                                      \
-    (!defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER))
-
 const Code::Comments& CreateCommentsFrom(compiler::Assembler* assembler) {
   const auto& comments = assembler->comments();
   Code::Comments& result = Code::Comments::New(comments.length());
@@ -21,7 +20,6 @@
   return result;
 }
 
+}  // namespace dart
 #endif  // !defined(DART_PRECOMPILED_RUNTIME) &&                               \
         // (!defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER))
-
-}  // namespace dart
diff --git a/runtime/vm/code_comments.h b/runtime/vm/code_comments.h
index 9c86524..18f0e75 100644
--- a/runtime/vm/code_comments.h
+++ b/runtime/vm/code_comments.h
@@ -5,15 +5,15 @@
 #ifndef RUNTIME_VM_CODE_COMMENTS_H_
 #define RUNTIME_VM_CODE_COMMENTS_H_
 
+#if !defined(DART_PRECOMPILED_RUNTIME) &&                                      \
+    (!defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER))
+
 #include "vm/code_observers.h"
 #include "vm/compiler/assembler/assembler.h"
 #include "vm/object.h"
 
 namespace dart {
 
-#if !defined(DART_PRECOMPILED_RUNTIME) &&                                      \
-    (!defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER))
-
 class CodeCommentsWrapper final : public CodeComments {
  public:
   explicit CodeCommentsWrapper(const Code::Comments& comments)
@@ -37,9 +37,9 @@
 
 const Code::Comments& CreateCommentsFrom(compiler::Assembler* assembler);
 
-#endif  // !defined(DART_PRECOMPILED_RUNTIME) &&                               \
-        // (!defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER))
 
 }  // namespace dart
 
+#endif  // !defined(DART_PRECOMPILED_RUNTIME) &&                               \
+        // (!defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER))
 #endif  // RUNTIME_VM_CODE_COMMENTS_H_
diff --git a/runtime/vm/code_descriptors.cc b/runtime/vm/code_descriptors.cc
index 5136d03..d773325 100644
--- a/runtime/vm/code_descriptors.cc
+++ b/runtime/vm/code_descriptors.cc
@@ -4,7 +4,7 @@
 
 #include "vm/code_descriptors.h"
 
-#include "vm/compiler/compiler_state.h"
+#include "vm/compiler/api/deopt_id.h"
 #include "vm/log.h"
 #include "vm/object_store.h"
 #include "vm/zone_text_buffer.h"
diff --git a/runtime/vm/code_patcher.h b/runtime/vm/code_patcher.h
index fa5c521..b9cc1c9 100644
--- a/runtime/vm/code_patcher.h
+++ b/runtime/vm/code_patcher.h
@@ -61,6 +61,11 @@
                                   const Code& caller_code,
                                   const Object& data,
                                   const Code& target);
+  static void PatchInstanceCallAtWithMutatorsStopped(Thread* thread,
+                                                     uword return_address,
+                                                     const Code& caller_code,
+                                                     const Object& data,
+                                                     const Code& target);
 
   // Return target of an unoptimized static call and its ICData object
   // (calls target via a stub).
@@ -78,6 +83,11 @@
                                     const Code& caller_code,
                                     const Object& data,
                                     const Code& target);
+  static void PatchSwitchableCallAtWithMutatorsStopped(Thread* thread,
+                                                       uword return_address,
+                                                       const Code& caller_code,
+                                                       const Object& data,
+                                                       const Code& target);
   static RawObject* GetSwitchableCallDataAt(uword return_address,
                                             const Code& caller_code);
   static RawCode* GetSwitchableCallTargetAt(uword return_address,
diff --git a/runtime/vm/code_patcher_arm.cc b/runtime/vm/code_patcher_arm.cc
index 024a6ab..52bdb2c 100644
--- a/runtime/vm/code_patcher_arm.cc
+++ b/runtime/vm/code_patcher_arm.cc
@@ -7,7 +7,6 @@
 
 #include "vm/code_patcher.h"
 
-#include "vm/compiler/backend/flow_graph_compiler.h"
 #include "vm/instructions.h"
 #include "vm/object.h"
 
@@ -47,6 +46,19 @@
                                       const Code& caller_code,
                                       const Object& data,
                                       const Code& target) {
+  auto thread = Thread::Current();
+  thread->isolate_group()->RunWithStoppedMutators([&]() {
+    PatchInstanceCallAtWithMutatorsStopped(thread, return_address, caller_code,
+                                           data, target);
+  });
+}
+
+void CodePatcher::PatchInstanceCallAtWithMutatorsStopped(
+    Thread* thread,
+    uword return_address,
+    const Code& caller_code,
+    const Object& data,
+    const Code& target) {
   ASSERT(caller_code.ContainsInstructionAt(return_address));
   ICCallPattern call(return_address, caller_code);
   call.SetData(data);
@@ -70,6 +82,20 @@
                                         const Code& caller_code,
                                         const Object& data,
                                         const Code& target) {
+  auto thread = Thread::Current();
+  // Ensure all threads are suspended as we update data and target pair.
+  thread->isolate_group()->RunWithStoppedMutators([&]() {
+    PatchSwitchableCallAtWithMutatorsStopped(thread, return_address,
+                                             caller_code, data, target);
+  });
+}
+
+void CodePatcher::PatchSwitchableCallAtWithMutatorsStopped(
+    Thread* thread,
+    uword return_address,
+    const Code& caller_code,
+    const Object& data,
+    const Code& target) {
   ASSERT(caller_code.ContainsInstructionAt(return_address));
   if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
     BareSwitchableCallPattern call(return_address, caller_code);
@@ -110,10 +136,12 @@
                                     const Code& code,
                                     NativeFunction target,
                                     const Code& trampoline) {
-  ASSERT(code.ContainsInstructionAt(return_address));
-  NativeCallPattern call(return_address, code);
-  call.set_target(trampoline);
-  call.set_native_function(target);
+  Thread::Current()->isolate_group()->RunWithStoppedMutators([&]() {
+    ASSERT(code.ContainsInstructionAt(return_address));
+    NativeCallPattern call(return_address, code);
+    call.set_target(trampoline);
+    call.set_native_function(target);
+  });
 }
 
 RawCode* CodePatcher::GetNativeCallAt(uword return_address,
diff --git a/runtime/vm/code_patcher_arm64.cc b/runtime/vm/code_patcher_arm64.cc
index ce3c4dc..abffa1b 100644
--- a/runtime/vm/code_patcher_arm64.cc
+++ b/runtime/vm/code_patcher_arm64.cc
@@ -82,6 +82,19 @@
                                       const Code& caller_code,
                                       const Object& data,
                                       const Code& target) {
+  auto thread = Thread::Current();
+  thread->isolate_group()->RunWithStoppedMutators([&]() {
+    PatchInstanceCallAtWithMutatorsStopped(thread, return_address, caller_code,
+                                           data, target);
+  });
+}
+
+void CodePatcher::PatchInstanceCallAtWithMutatorsStopped(
+    Thread* thread,
+    uword return_address,
+    const Code& caller_code,
+    const Object& data,
+    const Code& target) {
   ASSERT(caller_code.ContainsInstructionAt(return_address));
   ICCallPattern call(return_address, caller_code);
   call.SetData(data);
@@ -105,6 +118,20 @@
                                         const Code& caller_code,
                                         const Object& data,
                                         const Code& target) {
+  auto thread = Thread::Current();
+  // Ensure all threads are suspended as we update data and target pair.
+  thread->isolate_group()->RunWithStoppedMutators([&]() {
+    PatchSwitchableCallAtWithMutatorsStopped(thread, return_address,
+                                             caller_code, data, target);
+  });
+}
+
+void CodePatcher::PatchSwitchableCallAtWithMutatorsStopped(
+    Thread* thread,
+    uword return_address,
+    const Code& caller_code,
+    const Object& data,
+    const Code& target) {
   ASSERT(caller_code.ContainsInstructionAt(return_address));
   if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
     BareSwitchableCallPattern call(return_address, caller_code);
@@ -145,10 +172,12 @@
                                     const Code& caller_code,
                                     NativeFunction target,
                                     const Code& trampoline) {
-  ASSERT(caller_code.ContainsInstructionAt(return_address));
-  NativeCallPattern call(return_address, caller_code);
-  call.set_target(trampoline);
-  call.set_native_function(target);
+  Thread::Current()->isolate_group()->RunWithStoppedMutators([&]() {
+    ASSERT(caller_code.ContainsInstructionAt(return_address));
+    NativeCallPattern call(return_address, caller_code);
+    call.set_target(trampoline);
+    call.set_native_function(target);
+  });
 }
 
 RawCode* CodePatcher::GetNativeCallAt(uword return_address,
diff --git a/runtime/vm/code_patcher_ia32.cc b/runtime/vm/code_patcher_ia32.cc
index 7e97f67..a82d378 100644
--- a/runtime/vm/code_patcher_ia32.cc
+++ b/runtime/vm/code_patcher_ia32.cc
@@ -6,8 +6,6 @@
 #if defined(TARGET_ARCH_IA32)
 
 #include "vm/code_patcher.h"
-#include "vm/compiler/assembler/assembler.h"
-#include "vm/compiler/backend/flow_graph_compiler.h"
 #include "vm/cpu.h"
 #include "vm/dart_entry.h"
 #include "vm/instructions.h"
@@ -212,16 +210,26 @@
                                       const Object& data,
                                       const Code& target) {
   auto thread = Thread::Current();
+  thread->isolate_group()->RunWithStoppedMutators([&]() {
+    PatchInstanceCallAtWithMutatorsStopped(thread, return_address, caller_code,
+                                           data, target);
+  });
+}
+
+void CodePatcher::PatchInstanceCallAtWithMutatorsStopped(
+    Thread* thread,
+    uword return_address,
+    const Code& caller_code,
+    const Object& data,
+    const Code& target) {
   auto zone = thread->zone();
   ASSERT(caller_code.ContainsInstructionAt(return_address));
   const Instructions& instrs =
       Instructions::Handle(zone, caller_code.instructions());
-  thread->isolate_group()->RunWithStoppedMutators([&]() {
-    WritableInstructionsScope writable(instrs.PayloadStart(), instrs.Size());
-    InstanceCall call(return_address);
-    call.set_data(data);
-    call.set_target(target);
-  });
+  WritableInstructionsScope writable(instrs.PayloadStart(), instrs.Size());
+  InstanceCall call(return_address);
+  call.set_data(data);
+  call.set_target(target);
 }
 
 RawFunction* CodePatcher::GetUnoptimizedStaticCallAt(uword return_address,
@@ -245,6 +253,16 @@
   UNREACHABLE();
 }
 
+void CodePatcher::PatchSwitchableCallAtWithMutatorsStopped(
+    Thread* thread,
+    uword return_address,
+    const Code& caller_code,
+    const Object& data,
+    const Code& target) {
+  // Switchable instance calls only generated for precompilation.
+  UNREACHABLE();
+}
+
 RawCode* CodePatcher::GetSwitchableCallTargetAt(uword return_address,
                                                 const Code& caller_code) {
   // Switchable instance calls only generated for precompilation.
diff --git a/runtime/vm/code_patcher_x64.cc b/runtime/vm/code_patcher_x64.cc
index e67d187..b4c241c 100644
--- a/runtime/vm/code_patcher_x64.cc
+++ b/runtime/vm/code_patcher_x64.cc
@@ -6,8 +6,6 @@
 #if defined(TARGET_ARCH_X64)
 
 #include "vm/code_patcher.h"
-#include "vm/compiler/assembler/assembler.h"
-#include "vm/compiler/backend/flow_graph_compiler.h"
 #include "vm/cpu.h"
 #include "vm/dart_entry.h"
 #include "vm/instructions.h"
@@ -447,6 +445,19 @@
                                       const Code& caller_code,
                                       const Object& data,
                                       const Code& target) {
+  auto thread = Thread::Current();
+  thread->isolate_group()->RunWithStoppedMutators([&]() {
+    PatchInstanceCallAtWithMutatorsStopped(thread, return_address, caller_code,
+                                           data, target);
+  });
+}
+
+void CodePatcher::PatchInstanceCallAtWithMutatorsStopped(
+    Thread* thread,
+    uword return_address,
+    const Code& caller_code,
+    const Object& data,
+    const Code& target) {
   ASSERT(caller_code.ContainsInstructionAt(return_address));
   InstanceCall call(return_address, caller_code);
   call.set_data(data);
@@ -474,6 +485,20 @@
                                         const Code& caller_code,
                                         const Object& data,
                                         const Code& target) {
+  auto thread = Thread::Current();
+  // Ensure all threads are suspended as we update data and target pair.
+  thread->isolate_group()->RunWithStoppedMutators([&]() {
+    PatchSwitchableCallAtWithMutatorsStopped(thread, return_address,
+                                             caller_code, data, target);
+  });
+}
+
+void CodePatcher::PatchSwitchableCallAtWithMutatorsStopped(
+    Thread* thread,
+    uword return_address,
+    const Code& caller_code,
+    const Object& data,
+    const Code& target) {
   ASSERT(caller_code.ContainsInstructionAt(return_address));
   if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
     BareSwitchableCall call(return_address, caller_code);
@@ -514,10 +539,12 @@
                                     const Code& caller_code,
                                     NativeFunction target,
                                     const Code& trampoline) {
-  ASSERT(caller_code.ContainsInstructionAt(return_address));
-  NativeCall call(return_address, caller_code);
-  call.set_target(trampoline);
-  call.set_native_function(target);
+  Thread::Current()->isolate_group()->RunWithStoppedMutators([&]() {
+    ASSERT(caller_code.ContainsInstructionAt(return_address));
+    NativeCall call(return_address, caller_code);
+    call.set_target(trampoline);
+    call.set_native_function(target);
+  });
 }
 
 RawCode* CodePatcher::GetNativeCallAt(uword return_address,
diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc
index ec30d5d..0854325 100644
--- a/runtime/vm/compiler/aot/precompiler.cc
+++ b/runtime/vm/compiler/aot/precompiler.cc
@@ -353,42 +353,38 @@
           // We don't want the Array backing for any mappings in the snapshot,
           // only the pools themselves.
           I->object_store()->set_llvm_constant_hash_table(Array::null_array());
+
+          // Keep any functions, classes, etc. referenced from the LLVM pools,
+          // even if they could have been dropped due to not being otherwise
+          // needed at runtime.
+          const auto& constant_pool = GrowableObjectArray::Handle(
+              Z, I->object_store()->llvm_constant_pool());
+          auto& object = Object::Handle(Z);
+          for (intptr_t i = 0; i < constant_pool.Length(); i++) {
+            object = constant_pool.At(i);
+            if (object.IsNull()) continue;
+            if (object.IsInstance()) {
+              AddConstObject(Instance::Cast(object));
+            } else if (object.IsField()) {
+              AddField(Field::Cast(object));
+            } else if (object.IsFunction()) {
+              AddFunction(Function::Cast(object));
+            }
+          }
+
+          const auto& function_pool = GrowableObjectArray::Handle(
+              Z, I->object_store()->llvm_function_pool());
+          auto& function = Function::Handle(Z);
+          for (intptr_t i = 0; i < function_pool.Length(); i++) {
+            function ^= function_pool.At(i);
+            AddFunction(function);
+          }
         }
       }
 
       TraceForRetainedFunctions();
-
-      if (FLAG_use_bare_instructions && FLAG_use_table_dispatch) {
-        // Build the entries used to serialize the dispatch table before
-        // dropping functions, as we may clear references to Code objects.
-        const auto& entries =
-            Array::Handle(Z, dispatch_table_generator_->BuildCodeArray());
-        I->object_store()->set_dispatch_table_code_entries(entries);
-        // Delete the dispatch table generator to ensure there's no attempt
-        // to add new entries after this point.
-        delete dispatch_table_generator_;
-        dispatch_table_generator_ = nullptr;
-        if (!FLAG_retain_dispatched_functions && FLAG_trace_precompiler) {
-          FunctionSet printed(
-              HashTables::New<FunctionSet>(/*initial_capacity=*/1024));
-          auto& code = Code::Handle(Z);
-          auto& function = Function::Handle(Z);
-          for (intptr_t i = 0; i < entries.Length(); i++) {
-            code = Code::RawCast(entries.At(i));
-            if (code.IsNull()) continue;
-            if (!code.IsFunctionCode()) continue;
-            function = code.function();
-            ASSERT(!function.IsNull());
-            if (printed.ContainsKey(function)) continue;
-            if (functions_to_retain_.ContainsKey(function)) continue;
-            THR_Print(
-                "Dispatch table references code for function to drop: %s\n",
-                function.ToLibNamePrefixedQualifiedCString());
-            printed.Insert(function);
-          }
-          printed.Release();
-        }
-      }
+      FinalizeDispatchTable();
+      ReplaceFunctionPCRelativeCallEntries();
 
       DropFunctions();
       DropFields();
@@ -682,7 +678,9 @@
   for (auto& view : static_calls) {
     entry = view.Get<Code::kSCallTableFunctionTarget>();
     if (entry.IsFunction()) {
-      AddFunction(Function::Cast(entry));
+      AddFunction(Function::Cast(entry), FLAG_retain_function_objects);
+      ASSERT(view.Get<Code::kSCallTableCodeTarget>() == Code::null());
+      continue;
     }
     entry = view.Get<Code::kSCallTableCodeTarget>();
     if (entry.IsCode() && Code::Cast(entry).IsAllocationStubCode()) {
@@ -1304,7 +1302,7 @@
           AddFunction(function);
         }
         if (IsHitByTableSelector(function)) {
-          AddFunction(function, FLAG_retain_dispatched_functions);
+          AddFunction(function, FLAG_retain_function_objects);
         }
 
         bool found_metadata = false;
@@ -1528,6 +1526,85 @@
   }
 }
 
+void Precompiler::FinalizeDispatchTable() {
+  if (!FLAG_use_bare_instructions || !FLAG_use_table_dispatch) return;
+  // Build the entries used to serialize the dispatch table before
+  // dropping functions, as we may clear references to Code objects.
+  const auto& entries =
+      Array::Handle(Z, dispatch_table_generator_->BuildCodeArray());
+  I->object_store()->set_dispatch_table_code_entries(entries);
+  // Delete the dispatch table generator to ensure there's no attempt
+  // to add new entries after this point.
+  delete dispatch_table_generator_;
+  dispatch_table_generator_ = nullptr;
+
+  if (FLAG_retain_function_objects || !FLAG_trace_precompiler) return;
+
+  FunctionSet printed(HashTables::New<FunctionSet>(/*initial_capacity=*/1024));
+  auto& code = Code::Handle(Z);
+  auto& function = Function::Handle(Z);
+  for (intptr_t i = 0; i < entries.Length(); i++) {
+    code = Code::RawCast(entries.At(i));
+    if (code.IsNull()) continue;
+    if (!code.IsFunctionCode()) continue;
+    function = code.function();
+    ASSERT(!function.IsNull());
+    if (printed.ContainsKey(function)) continue;
+    if (functions_to_retain_.ContainsKey(function)) continue;
+    THR_Print("Dispatch table references code for function to drop: %s\n",
+              function.ToLibNamePrefixedQualifiedCString());
+    printed.Insert(function);
+  }
+  printed.Release();
+}
+
+void Precompiler::ReplaceFunctionPCRelativeCallEntries() {
+  class StaticCallTableEntryFixer : public CodeVisitor {
+   public:
+    explicit StaticCallTableEntryFixer(Zone* zone)
+        : table_(Array::Handle(zone)),
+          kind_and_offset_(Smi::Handle(zone)),
+          target_function_(Function::Handle(zone)),
+          target_code_(Code::Handle(zone)) {}
+
+    void VisitCode(const Code& code) {
+      if (!code.IsFunctionCode()) return;
+      table_ = code.static_calls_target_table();
+      StaticCallsTable static_calls(table_);
+      for (auto& view : static_calls) {
+        kind_and_offset_ = view.Get<Code::kSCallTableKindAndOffset>();
+        auto const kind = Code::KindField::decode(kind_and_offset_.Value());
+        if (kind != Code::kPcRelativeCall) continue;
+
+        target_function_ = view.Get<Code::kSCallTableFunctionTarget>();
+        if (target_function_.IsNull()) continue;
+
+        ASSERT(view.Get<Code::kSCallTableCodeTarget>() == Code::null());
+        ASSERT(target_function_.HasCode());
+        target_code_ = target_function_.CurrentCode();
+        ASSERT(!target_code_.IsStubCode());
+        view.Set<Code::kSCallTableCodeTarget>(target_code_);
+        view.Set<Code::kSCallTableFunctionTarget>(Object::null_function());
+        if (FLAG_trace_precompiler) {
+          THR_Print("Updated static call entry to %s in \"%s\"\n",
+                    target_function_.ToFullyQualifiedCString(),
+                    code.ToCString());
+        }
+      }
+    }
+
+   private:
+    Array& table_;
+    Smi& kind_and_offset_;
+    Function& target_function_;
+    Code& target_code_;
+  };
+
+  HANDLESCOPE(T);
+  StaticCallTableEntryFixer visitor(Z);
+  ProgramVisitor::WalkProgram(Z, I, &visitor);
+}
+
 void Precompiler::DropFunctions() {
   Library& lib = Library::Handle(Z);
   Class& cls = Class::Handle(Z);
@@ -2006,7 +2083,6 @@
 void Precompiler::DropClasses() {
   Class& cls = Class::Handle(Z);
   Array& constants = Array::Handle(Z);
-  const Script& null_script = Script::Handle(Z);
 
   // We are about to remove classes from the class table. For this to be safe,
   // there must be no instances of these classes on the heap, not even
@@ -2049,7 +2125,6 @@
 
     class_table->Unregister(cid);
     cls.set_id(kIllegalCid);  // We check this when serializing.
-    cls.set_script(null_script);
   }
 }
 
@@ -2060,7 +2135,6 @@
       Library::Handle(Z, I->object_store()->root_library());
   Library& lib = Library::Handle(Z);
   Class& toplevel_class = Class::Handle(Z);
-  const Script& null_script = Script::Handle(Z);
 
   for (intptr_t i = 0; i < libraries_.Length(); i++) {
     lib ^= libraries_.At(i);
@@ -2098,7 +2172,6 @@
 
       I->class_table()->Unregister(toplevel_class.id());
       toplevel_class.set_id(kIllegalCid);  // We check this when serializing.
-      toplevel_class.set_script(null_script);
 
       dropped_library_count_++;
       lib.set_index(-1);
diff --git a/runtime/vm/compiler/aot/precompiler.h b/runtime/vm/compiler/aot/precompiler.h
index 36a600d..8de467a 100644
--- a/runtime/vm/compiler/aot/precompiler.h
+++ b/runtime/vm/compiler/aot/precompiler.h
@@ -257,6 +257,8 @@
   void AttachOptimizedTypeTestingStub();
 
   void TraceForRetainedFunctions();
+  void FinalizeDispatchTable();
+  void ReplaceFunctionPCRelativeCallEntries();
   void DropFunctions();
   void DropFields();
   void TraceTypesFromRetainedClasses();
diff --git a/runtime/vm/compiler/api/deopt_id.h b/runtime/vm/compiler/api/deopt_id.h
new file mode 100644
index 0000000..c203e45
--- /dev/null
+++ b/runtime/vm/compiler/api/deopt_id.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#ifndef RUNTIME_VM_COMPILER_API_DEOPT_ID_H_
+#define RUNTIME_VM_COMPILER_API_DEOPT_ID_H_
+
+#include "platform/allocation.h"
+
+namespace dart {
+
+// Deoptimization Id logic.
+//
+// Deoptimization ids are used to refer to deoptimization points, at which
+// control can enter unoptimized code from the optimized version of the code.
+//
+// Note: any instruction that does a call has two deoptimization points,
+// one before the call and one after the call - so that we could deoptimize
+// to either before or after the call depending on whether the same call
+// already occured in the optimized code (and potentially produced
+// observable side-effects) or not.
+//
+// To simplify implementation we always allocate two deopt ids (one for before
+// point and one for the after point).
+class DeoptId : public AllStatic {
+ public:
+  static constexpr intptr_t kNone = -1;
+
+  static inline intptr_t Next(intptr_t deopt_id) { return deopt_id + kStep; }
+
+  static inline intptr_t ToDeoptAfter(intptr_t deopt_id) {
+    ASSERT(IsDeoptBefore(deopt_id));
+    return deopt_id + kAfterOffset;
+  }
+
+  static inline bool IsDeoptBefore(intptr_t deopt_id) {
+    return (deopt_id % kStep) == kBeforeOffset;
+  }
+
+  static inline bool IsDeoptAfter(intptr_t deopt_id) {
+    return (deopt_id % kStep) == kAfterOffset;
+  }
+
+ private:
+  static constexpr intptr_t kStep = 2;
+  static constexpr intptr_t kBeforeOffset = 0;
+  static constexpr intptr_t kAfterOffset = 1;
+};
+
+}  // namespace dart
+
+#endif  // RUNTIME_VM_COMPILER_API_DEOPT_ID_H_
diff --git a/runtime/vm/compiler/api/print_filter.cc b/runtime/vm/compiler/api/print_filter.cc
new file mode 100644
index 0000000..ee2a06f
--- /dev/null
+++ b/runtime/vm/compiler/api/print_filter.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+#if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
+
+#include "vm/compiler/api/print_filter.h"
+
+#include "vm/flags.h"
+#include "vm/object.h"
+
+namespace dart {
+
+DEFINE_FLAG(charp,
+            print_flow_graph_filter,
+            NULL,
+            "Print only IR of functions with matching names");
+
+namespace compiler {
+
+// Checks whether function's name matches the given filter, which is
+// a comma-separated list of strings.
+static bool PassesFilter(const char* filter, const Function& function) {
+  if (filter == NULL) {
+    return true;
+  }
+
+  char* save_ptr;  // Needed for strtok_r.
+  const char* scrubbed_name =
+      String::Handle(function.QualifiedScrubbedName()).ToCString();
+  const char* function_name = function.ToFullyQualifiedCString();
+  intptr_t function_name_len = strlen(function_name);
+
+  intptr_t len = strlen(filter) + 1;  // Length with \0.
+  char* filter_buffer = new char[len];
+  strncpy(filter_buffer, filter, len);  // strtok modifies arg 1.
+  char* token = strtok_r(filter_buffer, ",", &save_ptr);
+  bool found = false;
+  while (token != NULL) {
+    if ((strstr(function_name, token) != NULL) ||
+        (strstr(scrubbed_name, token) != NULL)) {
+      found = true;
+      break;
+    }
+    const intptr_t token_len = strlen(token);
+    if (token[token_len - 1] == '%') {
+      if (function_name_len > token_len) {
+        const char* suffix =
+            function_name + (function_name_len - token_len + 1);
+        if (strncmp(suffix, token, token_len - 1) == 0) {
+          found = true;
+          break;
+        }
+      }
+    }
+    token = strtok_r(NULL, ",", &save_ptr);
+  }
+  delete[] filter_buffer;
+
+  return found;
+}
+
+bool PrintFilter::ShouldPrint(const Function& function) {
+  return PassesFilter(FLAG_print_flow_graph_filter, function);
+}
+
+}  // namespace compiler
+
+}  // namespace dart
+
+#endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
diff --git a/runtime/vm/compiler/api/print_filter.h b/runtime/vm/compiler/api/print_filter.h
new file mode 100644
index 0000000..1eea14a
--- /dev/null
+++ b/runtime/vm/compiler/api/print_filter.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#ifndef RUNTIME_VM_COMPILER_API_PRINT_FILTER_H_
+#define RUNTIME_VM_COMPILER_API_PRINT_FILTER_H_
+#if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
+
+#include "platform/allocation.h"
+
+namespace dart {
+
+class Function;
+
+namespace compiler {
+
+class PrintFilter : public AllStatic {
+ public:
+  static bool ShouldPrint(const Function& function);
+};
+
+}  // namespace compiler
+
+}  // namespace dart
+
+#endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
+#endif  // RUNTIME_VM_COMPILER_API_PRINT_FILTER_H_
diff --git a/runtime/vm/compiler/api/type_check_mode.h b/runtime/vm/compiler/api/type_check_mode.h
new file mode 100644
index 0000000..7710607
--- /dev/null
+++ b/runtime/vm/compiler/api/type_check_mode.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#ifndef RUNTIME_VM_COMPILER_API_TYPE_CHECK_MODE_H_
+#define RUNTIME_VM_COMPILER_API_TYPE_CHECK_MODE_H_
+
+namespace dart {
+
+// Invocation mode for TypeCheck runtime entry that describes
+// where we are calling it from.
+enum TypeCheckMode {
+  // TypeCheck is invoked from LazySpecializeTypeTest stub.
+  // It should replace stub on the type with a specialized version.
+  kTypeCheckFromLazySpecializeStub,
+
+  // TypeCheck is invoked from the SlowTypeTest stub.
+  // This means that cache can be lazily created (if needed)
+  // and dst_name can be fetched from the pool.
+  kTypeCheckFromSlowStub,
+
+  // TypeCheck is invoked from normal inline AssertAssignable.
+  // Both cache and dst_name must be already populated.
+  kTypeCheckFromInline
+};
+
+}  // namespace dart
+
+#endif  // RUNTIME_VM_COMPILER_API_TYPE_CHECK_MODE_H_
diff --git a/runtime/vm/compiler/asm_intrinsifier_arm.cc b/runtime/vm/compiler/asm_intrinsifier_arm.cc
index 6fe378e..86679cb 100644
--- a/runtime/vm/compiler/asm_intrinsifier_arm.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_arm.cc
@@ -1583,21 +1583,24 @@
   __ b(&not_double, NE);
 
   __ LoadIsolate(R0);
-  __ LoadFromOffset(kWord, R0, R0, target::Isolate::object_store_offset());
+  __ LoadFromOffset(kWord, R0, R0,
+                    target::Isolate::cached_object_store_offset());
   __ LoadFromOffset(kWord, R0, R0, target::ObjectStore::double_type_offset());
   __ Ret();
 
   __ Bind(&not_double);
   JumpIfNotInteger(assembler, R1, R0, &not_integer);
   __ LoadIsolate(R0);
-  __ LoadFromOffset(kWord, R0, R0, target::Isolate::object_store_offset());
+  __ LoadFromOffset(kWord, R0, R0,
+                    target::Isolate::cached_object_store_offset());
   __ LoadFromOffset(kWord, R0, R0, target::ObjectStore::int_type_offset());
   __ Ret();
 
   __ Bind(&not_integer);
   JumpIfNotString(assembler, R1, R0, &use_declaration_type);
   __ LoadIsolate(R0);
-  __ LoadFromOffset(kWord, R0, R0, target::Isolate::object_store_offset());
+  __ LoadFromOffset(kWord, R0, R0,
+                    target::Isolate::cached_object_store_offset());
   __ LoadFromOffset(kWord, R0, R0, target::ObjectStore::string_type_offset());
   __ Ret();
 
diff --git a/runtime/vm/compiler/asm_intrinsifier_arm64.cc b/runtime/vm/compiler/asm_intrinsifier_arm64.cc
index db838ee..aaf35b7 100644
--- a/runtime/vm/compiler/asm_intrinsifier_arm64.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_arm64.cc
@@ -1648,21 +1648,21 @@
   __ b(&not_double, NE);
 
   __ LoadIsolate(R0);
-  __ LoadFromOffset(R0, R0, target::Isolate::object_store_offset());
+  __ LoadFromOffset(R0, R0, target::Isolate::cached_object_store_offset());
   __ LoadFromOffset(R0, R0, target::ObjectStore::double_type_offset());
   __ ret();
 
   __ Bind(&not_double);
   JumpIfNotInteger(assembler, R1, R0, &not_integer);
   __ LoadIsolate(R0);
-  __ LoadFromOffset(R0, R0, target::Isolate::object_store_offset());
+  __ LoadFromOffset(R0, R0, target::Isolate::cached_object_store_offset());
   __ LoadFromOffset(R0, R0, target::ObjectStore::int_type_offset());
   __ ret();
 
   __ Bind(&not_integer);
   JumpIfNotString(assembler, R1, R0, &use_declaration_type);
   __ LoadIsolate(R0);
-  __ LoadFromOffset(R0, R0, target::Isolate::object_store_offset());
+  __ LoadFromOffset(R0, R0, target::Isolate::cached_object_store_offset());
   __ LoadFromOffset(R0, R0, target::ObjectStore::string_type_offset());
   __ ret();
 
diff --git a/runtime/vm/compiler/asm_intrinsifier_ia32.cc b/runtime/vm/compiler/asm_intrinsifier_ia32.cc
index c4fb55b..edcc866 100644
--- a/runtime/vm/compiler/asm_intrinsifier_ia32.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_ia32.cc
@@ -1682,7 +1682,7 @@
   __ j(NOT_EQUAL, &not_double);
 
   __ LoadIsolate(EAX);
-  __ movl(EAX, Address(EAX, target::Isolate::object_store_offset()));
+  __ movl(EAX, Address(EAX, target::Isolate::cached_object_store_offset()));
   __ movl(EAX, Address(EAX, target::ObjectStore::double_type_offset()));
   __ ret();
 
@@ -1692,7 +1692,7 @@
   JumpIfNotInteger(assembler, EAX, &not_integer);
 
   __ LoadIsolate(EAX);
-  __ movl(EAX, Address(EAX, target::Isolate::object_store_offset()));
+  __ movl(EAX, Address(EAX, target::Isolate::cached_object_store_offset()));
   __ movl(EAX, Address(EAX, target::ObjectStore::int_type_offset()));
   __ ret();
 
@@ -1703,7 +1703,7 @@
   JumpIfNotString(assembler, EAX, &use_declaration_type);
 
   __ LoadIsolate(EAX);
-  __ movl(EAX, Address(EAX, target::Isolate::object_store_offset()));
+  __ movl(EAX, Address(EAX, target::Isolate::cached_object_store_offset()));
   __ movl(EAX, Address(EAX, target::ObjectStore::string_type_offset()));
   __ ret();
 
diff --git a/runtime/vm/compiler/asm_intrinsifier_x64.cc b/runtime/vm/compiler/asm_intrinsifier_x64.cc
index 09b3e10..c4dc1b6 100644
--- a/runtime/vm/compiler/asm_intrinsifier_x64.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_x64.cc
@@ -1594,7 +1594,7 @@
   __ j(NOT_EQUAL, &not_double);
 
   __ LoadIsolate(RAX);
-  __ movq(RAX, Address(RAX, target::Isolate::object_store_offset()));
+  __ movq(RAX, Address(RAX, target::Isolate::cached_object_store_offset()));
   __ movq(RAX, Address(RAX, target::ObjectStore::double_type_offset()));
   __ ret();
 
@@ -1604,7 +1604,7 @@
   JumpIfNotInteger(assembler, RAX, &not_integer);
 
   __ LoadIsolate(RAX);
-  __ movq(RAX, Address(RAX, target::Isolate::object_store_offset()));
+  __ movq(RAX, Address(RAX, target::Isolate::cached_object_store_offset()));
   __ movq(RAX, Address(RAX, target::ObjectStore::int_type_offset()));
   __ ret();
 
@@ -1615,7 +1615,7 @@
   JumpIfNotString(assembler, RAX, &use_declaration_type);
 
   __ LoadIsolate(RAX);
-  __ movq(RAX, Address(RAX, target::Isolate::object_store_offset()));
+  __ movq(RAX, Address(RAX, target::Isolate::cached_object_store_offset()));
   __ movq(RAX, Address(RAX, target::ObjectStore::string_type_offset()));
   __ ret();
 
diff --git a/runtime/vm/compiler/assembler/assembler_arm.cc b/runtime/vm/compiler/assembler/assembler_arm.cc
index 9c0caf9..35f8ced 100644
--- a/runtime/vm/compiler/assembler/assembler_arm.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm.cc
@@ -2004,8 +2004,8 @@
 void Assembler::LoadClassById(Register result, Register class_id) {
   ASSERT(result != class_id);
 
-  const intptr_t table_offset = target::Isolate::class_table_offset() +
-                                target::ClassTable::table_offset();
+  const intptr_t table_offset =
+      target::Isolate::cached_class_table_table_offset();
 
   LoadIsolate(result);
   LoadFromOffset(kWord, result, result, table_offset);
@@ -3529,8 +3529,7 @@
   ASSERT(cid > 0);
 
   const intptr_t shared_table_offset =
-      target::Isolate::class_table_offset() +
-      target::ClassTable::shared_class_table_offset();
+      target::Isolate::shared_class_table_offset();
   const intptr_t table_offset =
       target::SharedClassTable::class_heap_stats_table_offset();
   const intptr_t class_offset = target::ClassTable::ClassOffsetFor(cid);
diff --git a/runtime/vm/compiler/assembler/assembler_arm.h b/runtime/vm/compiler/assembler/assembler_arm.h
index bd79c03..81ed9f6 100644
--- a/runtime/vm/compiler/assembler/assembler_arm.h
+++ b/runtime/vm/compiler/assembler/assembler_arm.h
@@ -589,8 +589,6 @@
            B4 | (imm16 & 0xf);
   }
 
-  static uword GetBreakInstructionFiller() { return BkptEncoding(0); }
-
   // Floating point instructions (VFPv3-D16 and VFPv3-D32 profiles).
   void vmovsr(SRegister sn, Register rt, Condition cond = AL);
   void vmovrs(Register rt, SRegister sn, Condition cond = AL);
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.cc b/runtime/vm/compiler/assembler/assembler_arm64.cc
index 422ba5a..a22a4fa 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm64.cc
@@ -1155,8 +1155,8 @@
 void Assembler::LoadClassById(Register result, Register class_id) {
   ASSERT(result != class_id);
 
-  const intptr_t table_offset = target::Isolate::class_table_offset() +
-                                target::ClassTable::table_offset();
+  const intptr_t table_offset =
+      target::Isolate::cached_class_table_table_offset();
 
   LoadIsolate(result);
   LoadFromOffset(result, result, table_offset);
@@ -1631,8 +1631,7 @@
   ASSERT(cid > 0);
 
   const intptr_t shared_table_offset =
-      target::Isolate::class_table_offset() +
-      target::ClassTable::shared_class_table_offset();
+      target::Isolate::shared_class_table_offset();
   const intptr_t table_offset =
       target::SharedClassTable::class_heap_stats_table_offset();
   const intptr_t class_offset = target::ClassTable::ClassOffsetFor(cid);
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.h b/runtime/vm/compiler/assembler/assembler_arm64.h
index 26ab378..071bb36 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.h
+++ b/runtime/vm/compiler/assembler/assembler_arm64.h
@@ -1034,11 +1034,6 @@
   // Breakpoint.
   void brk(uint16_t imm) { EmitExceptionGenOp(BRK, imm); }
 
-  static uword GetBreakInstructionFiller() {
-    const intptr_t encoding = ExceptionGenOpEncoding(BRK, 0);
-    return encoding << 32 | encoding;
-  }
-
   // Double floating point.
   bool fmovdi(VRegister vd, double immd) {
     int64_t imm64 = bit_cast<int64_t, double>(immd);
diff --git a/runtime/vm/compiler/assembler/assembler_ia32.cc b/runtime/vm/compiler/assembler/assembler_ia32.cc
index 74d3316..1ef9cda 100644
--- a/runtime/vm/compiler/assembler/assembler_ia32.cc
+++ b/runtime/vm/compiler/assembler/assembler_ia32.cc
@@ -2409,8 +2409,7 @@
   Address state_address(kNoRegister, 0);
 
   const intptr_t shared_table_offset =
-      target::Isolate::class_table_offset() +
-      target::ClassTable::shared_class_table_offset();
+      target::Isolate::shared_class_table_offset();
   const intptr_t table_offset =
       target::SharedClassTable::class_heap_stats_table_offset();
   const intptr_t class_offset = target::ClassTable::ClassOffsetFor(cid);
@@ -2638,8 +2637,8 @@
 void Assembler::LoadClassById(Register result, Register class_id) {
   ASSERT(result != class_id);
 
-  const intptr_t table_offset = target::Isolate::class_table_offset() +
-                                target::ClassTable::table_offset();
+  const intptr_t table_offset =
+      target::Isolate::cached_class_table_table_offset();
   LoadIsolate(result);
   movl(result, Address(result, table_offset));
   movl(result, Address(result, class_id, TIMES_4, 0));
diff --git a/runtime/vm/compiler/assembler/assembler_ia32.h b/runtime/vm/compiler/assembler/assembler_ia32.h
index c60968e..286b6f9 100644
--- a/runtime/vm/compiler/assembler/assembler_ia32.h
+++ b/runtime/vm/compiler/assembler/assembler_ia32.h
@@ -549,8 +549,6 @@
   void int3();
   void hlt();
 
-  static uword GetBreakInstructionFiller() { return 0xCCCCCCCC; }
-
   void j(Condition condition, Label* label, bool near = kFarJump);
   void j(Condition condition, const ExternalLabel* label);
 
diff --git a/runtime/vm/compiler/assembler/assembler_x64.cc b/runtime/vm/compiler/assembler/assembler_x64.cc
index 60802f9..a7b9cb5 100644
--- a/runtime/vm/compiler/assembler/assembler_x64.cc
+++ b/runtime/vm/compiler/assembler/assembler_x64.cc
@@ -1864,8 +1864,7 @@
                                      bool near_jump) {
   ASSERT(cid > 0);
   const intptr_t shared_table_offset =
-      target::Isolate::class_table_offset() +
-      target::ClassTable::shared_class_table_offset();
+      target::Isolate::shared_class_table_offset();
   const intptr_t table_offset =
       target::SharedClassTable::class_heap_stats_table_offset();
   const intptr_t class_offset = target::ClassTable::ClassOffsetFor(cid);
@@ -2139,8 +2138,8 @@
 
 void Assembler::LoadClassById(Register result, Register class_id) {
   ASSERT(result != class_id);
-  const intptr_t table_offset = target::Isolate::class_table_offset() +
-                                target::ClassTable::table_offset();
+  const intptr_t table_offset =
+      target::Isolate::cached_class_table_table_offset();
 
   LoadIsolate(result);
   movq(result, Address(result, table_offset));
diff --git a/runtime/vm/compiler/assembler/assembler_x64.h b/runtime/vm/compiler/assembler/assembler_x64.h
index 0c281ca..42a17d8 100644
--- a/runtime/vm/compiler/assembler/assembler_x64.h
+++ b/runtime/vm/compiler/assembler/assembler_x64.h
@@ -642,8 +642,6 @@
   // 'size' indicates size in bytes and must be in the range 1..8.
   void nop(int size = 1);
 
-  static uword GetBreakInstructionFiller() { return 0xCCCCCCCCCCCCCCCC; }
-
   void j(Condition condition, Label* label, bool near = kFarJump);
   void jmp(Register reg) { EmitUnaryL(reg, 0xFF, 4); }
   void jmp(const Address& address) { EmitUnaryL(address, 0xFF, 4); }
diff --git a/runtime/vm/compiler/assembler/disassembler.cc b/runtime/vm/compiler/assembler/disassembler.cc
index 37c3743..89e9204 100644
--- a/runtime/vm/compiler/assembler/disassembler.cc
+++ b/runtime/vm/compiler/assembler/disassembler.cc
@@ -5,8 +5,6 @@
 #include "vm/compiler/assembler/disassembler.h"
 
 #include "vm/code_patcher.h"
-#include "vm/compiler/assembler/assembler.h"
-#include "vm/compiler/backend/il_printer.h"
 #include "vm/deopt_instructions.h"
 #include "vm/globals.h"
 #include "vm/instructions.h"
@@ -18,7 +16,10 @@
 
 #if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
 DECLARE_FLAG(bool, trace_inlining_intervals);
+#endif
+
 DEFINE_FLAG(bool, trace_source_positions, false, "Source position diagnostics");
 
 void DisassembleToStdout::ConsumeInstruction(char* hex_buffer,
@@ -416,9 +417,12 @@
   }
 #endif  // defined(DART_PRECOMPILED_RUNTIME)
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
   if (optimized && FLAG_trace_inlining_intervals) {
     code.DumpInlineIntervals();
   }
+#endif
+
   if (FLAG_trace_source_positions) {
     code.DumpSourcePositions();
   }
diff --git a/runtime/vm/compiler/assembler/disassembler.h b/runtime/vm/compiler/assembler/disassembler.h
index 861051d..4a86177 100644
--- a/runtime/vm/compiler/assembler/disassembler.h
+++ b/runtime/vm/compiler/assembler/disassembler.h
@@ -6,11 +6,14 @@
 #define RUNTIME_VM_COMPILER_ASSEMBLER_DISASSEMBLER_H_
 
 #include "vm/allocation.h"
-#include "vm/compiler/assembler/assembler.h"
 #include "vm/globals.h"
 #include "vm/log.h"
 #include "vm/object.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/assembler/assembler.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 // Forward declaration.
diff --git a/runtime/vm/compiler/assembler/disassembler_x86.cc b/runtime/vm/compiler/assembler/disassembler_x86.cc
index b51f464..0d162a5 100644
--- a/runtime/vm/compiler/assembler/disassembler_x86.cc
+++ b/runtime/vm/compiler/assembler/disassembler_x86.cc
@@ -11,6 +11,7 @@
 
 #include "platform/utils.h"
 #include "vm/allocation.h"
+#include "vm/constants_x86.h"
 #include "vm/heap/heap.h"
 #include "vm/instructions.h"
 #include "vm/os.h"
diff --git a/runtime/vm/compiler/backend/constant_propagator.cc b/runtime/vm/compiler/backend/constant_propagator.cc
index 42382a8..ca953b5 100644
--- a/runtime/vm/compiler/backend/constant_propagator.cc
+++ b/runtime/vm/compiler/backend/constant_propagator.cc
@@ -38,7 +38,8 @@
       non_constant_(Object::non_constant()),
       constant_value_(Object::Handle(Z)),
       reachable_(new (Z) BitVector(Z, graph->preorder().length())),
-      marked_phis_(new (Z) BitVector(Z, graph->max_virtual_register_number())),
+      unwrapped_phis_(new (Z)
+                          BitVector(Z, graph->max_virtual_register_number())),
       block_worklist_(),
       definition_worklist_(graph, 10) {}
 
@@ -220,7 +221,16 @@
   // Phi value depends on the reachability of a predecessor. We have
   // to revisit phis every time a predecessor becomes reachable.
   for (PhiIterator it(instr->successor()); !it.Done(); it.Advance()) {
-    it.Current()->Accept(this);
+    PhiInstr* phi = it.Current();
+    phi->Accept(this);
+
+    // If this phi was previously unwrapped as redundant and it is no longer
+    // redundant (does not unwrap) then we need to revisit the uses.
+    if (unwrapped_phis_->Contains(phi->ssa_temp_index()) &&
+        (UnwrapPhi(phi) == phi)) {
+      unwrapped_phis_->Remove(phi->ssa_temp_index());
+      definition_worklist_.Add(phi);
+    }
   }
 }
 
@@ -315,9 +325,25 @@
   return defn;
 }
 
-void ConstantPropagator::MarkPhi(Definition* phi) {
+void ConstantPropagator::MarkUnwrappedPhi(Definition* phi) {
   ASSERT(phi->IsPhi());
-  marked_phis_->Add(phi->ssa_temp_index());
+  unwrapped_phis_->Add(phi->ssa_temp_index());
+}
+
+ConstantPropagator::PhiInfo* ConstantPropagator::GetPhiInfo(PhiInstr* phi) {
+  if (phi->HasPassSpecificId(CompilerPass::kConstantPropagation)) {
+    const intptr_t id =
+        phi->GetPassSpecificId(CompilerPass::kConstantPropagation);
+    // Note: id might have been assigned by the previous round of constant
+    // propagation, so we need to verify it before using it.
+    if (id < phis_.length() && phis_[id].phi == phi) {
+      return &phis_[id];
+    }
+  }
+
+  phi->SetPassSpecificId(CompilerPass::kConstantPropagation, phis_.length());
+  phis_.Add({phi, 0});
+  return &phis_.Last();
 }
 
 // --------------------------------------------------------------------------
@@ -325,6 +351,29 @@
 // and the definition has input uses, add the definition to the definition
 // worklist so that the used can be processed.
 void ConstantPropagator::VisitPhi(PhiInstr* instr) {
+  // Detect convergence issues by checking if visit count for this phi
+  // is too high. We should only visit this phi once for every predecessor
+  // becoming reachable, once for every input changing its constant value and
+  // once for an unwrapped redundant phi becoming non-redundant.
+  // Inputs can only change their constant value at most three times: from
+  // non-constant to unknown to specific constant to non-constant. The first
+  // link (non-constant to ...) can happen when we run the second round of
+  // constant propagation - some instructions can have non-constant assigned to
+  // them at the end of the previous constant propagation.
+  auto info = GetPhiInfo(instr);
+  info->visit_count++;
+  const intptr_t kMaxVisitsExpected = 5 * instr->InputCount();
+  if (info->visit_count > kMaxVisitsExpected) {
+    OS::PrintErr(
+        "ConstantPropagation pass is failing to converge on graph for %s\n",
+        graph_->parsed_function().function().ToCString());
+    OS::PrintErr("Phi %s was visited %" Pd " times\n", instr->ToCString(),
+                 info->visit_count);
+    NOT_IN_PRODUCT(
+        FlowGraphPrinter::PrintGraph("Constant Propagation", graph_));
+    FATAL("Aborting due to non-covergence.");
+  }
+
   // Compute the join over all the reachable predecessor values.
   JoinEntryInstr* block = instr->block();
   Object& value = Object::ZoneHandle(Z, Unknown());
@@ -334,11 +383,7 @@
       Join(&value, instr->InputAt(pred_idx)->definition()->constant_value());
     }
   }
-  if (!SetValue(instr, value) &&
-      marked_phis_->Contains(instr->ssa_temp_index())) {
-    marked_phis_->Remove(instr->ssa_temp_index());
-    definition_worklist_.Add(instr);
-  }
+  SetValue(instr, value);
 }
 
 void ConstantPropagator::VisitRedefinition(RedefinitionInstr* instr) {
@@ -523,12 +568,13 @@
   Definition* unwrapped_right_defn = UnwrapPhi(right_defn);
   if (unwrapped_left_defn == unwrapped_right_defn) {
     // Fold x === x, and x !== x to true/false.
-    SetValue(instr, Bool::Get(instr->kind() == Token::kEQ_STRICT));
-    if (unwrapped_left_defn != left_defn) {
-      MarkPhi(left_defn);
-    }
-    if (unwrapped_right_defn != right_defn) {
-      MarkPhi(right_defn);
+    if (SetValue(instr, Bool::Get(instr->kind() == Token::kEQ_STRICT))) {
+      if (unwrapped_left_defn != left_defn) {
+        MarkUnwrappedPhi(left_defn);
+      }
+      if (unwrapped_right_defn != right_defn) {
+        MarkUnwrappedPhi(right_defn);
+      }
     }
     return;
   }
@@ -626,12 +672,13 @@
     Definition* unwrapped_right_defn = UnwrapPhi(right_defn);
     if (unwrapped_left_defn == unwrapped_right_defn) {
       // Fold x === x, and x !== x to true/false.
-      SetValue(instr, Bool::Get(instr->kind() == Token::kEQ));
-      if (unwrapped_left_defn != left_defn) {
-        MarkPhi(left_defn);
-      }
-      if (unwrapped_right_defn != right_defn) {
-        MarkPhi(right_defn);
+      if (SetValue(instr, Bool::Get(instr->kind() == Token::kEQ))) {
+        if (unwrapped_left_defn != left_defn) {
+          MarkUnwrappedPhi(left_defn);
+        }
+        if (unwrapped_right_defn != right_defn) {
+          MarkUnwrappedPhi(right_defn);
+        }
       }
       return;
     }
diff --git a/runtime/vm/compiler/backend/constant_propagator.h b/runtime/vm/compiler/backend/constant_propagator.h
index eda356b..dd32d16 100644
--- a/runtime/vm/compiler/backend/constant_propagator.h
+++ b/runtime/vm/compiler/backend/constant_propagator.h
@@ -45,8 +45,12 @@
   void SetReachable(BlockEntryInstr* block);
   bool SetValue(Definition* definition, const Object& value);
 
+  // Phi might be viewed as redundant based on current reachability of
+  // predecessor blocks (i.e. the same definition is flowing from all
+  // reachable predecessors). We can use this information to constant
+  // fold phi(x) == x and phi(x) != x comparisons.
   Definition* UnwrapPhi(Definition* defn);
-  void MarkPhi(Definition* defn);
+  void MarkUnwrappedPhi(Definition* defn);
 
   // Assign the join (least upper bound) of a pair of abstract values to the
   // first one.
@@ -67,7 +71,18 @@
 
 #define DECLARE_VISIT(type, attrs) virtual void Visit##type(type##Instr* instr);
   FOR_EACH_INSTRUCTION(DECLARE_VISIT)
+
 #undef DECLARE_VISIT
+  // Structure tracking visit counts for phis. Used to detect infinite loops.
+  struct PhiInfo {
+    PhiInstr* phi;
+    intptr_t visit_count;
+  };
+
+  // Returns PhiInfo associated with the given phi. Note that this
+  // pointer can be invalidated by subsequent call to GetPhiInfo and
+  // thus should not be stored anywhere.
+  PhiInfo* GetPhiInfo(PhiInstr* phi);
 
   Isolate* isolate() const { return graph_->isolate(); }
 
@@ -84,7 +99,15 @@
   // preorder number.
   BitVector* reachable_;
 
-  BitVector* marked_phis_;
+  // Bitvector of phis that were "unwrapped" into one of their inputs
+  // when visiting one of their uses. These uses of these phis
+  // should be revisited if reachability of the predecessor blocks
+  // changes even if that does not change constant value of the phi.
+  BitVector* unwrapped_phis_;
+
+  // List of visited phis indexed by their id (stored as pass specific id on
+  // a phi instruction).
+  GrowableArray<PhiInfo> phis_;
 
   // Worklists of blocks and definitions.
   GrowableArray<BlockEntryInstr*> block_worklist_;
diff --git a/runtime/vm/compiler/backend/constant_propagator_test.cc b/runtime/vm/compiler/backend/constant_propagator_test.cc
new file mode 100644
index 0000000..40db6b4
--- /dev/null
+++ b/runtime/vm/compiler/backend/constant_propagator_test.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include "vm/compiler/backend/constant_propagator.h"
+
+#include "vm/compiler/backend/block_builder.h"
+#include "vm/compiler/backend/il_test_helper.h"
+#include "vm/unit_test.h"
+
+namespace dart {
+
+// Test issue https://github.com/flutter/flutter/issues/53903.
+//
+// If graph contains a cyclic phi which participates in an EqualityCompare
+// or StrictCompare with its input like phi(x, ...) == x then constant
+// propagation might fail to converge by constantly revisiting this phi and
+// its uses (which includes comparison and the phi itself).
+ISOLATE_UNIT_TEST_CASE(ConstantPropagation_PhiUnwrappingAndConvergence) {
+  using compiler::BlockBuilder;
+  CompilerState S(thread, /*is_aot=*/false);
+  FlowGraphBuilderHelper H;
+
+  // We are going to build the following graph:
+  //
+  // B0[graph_entry]
+  // B1[function_entry]:
+  //   v0 <- Constant(0)
+  //   goto B2
+  // B2:
+  //   v1 <- phi(v0, v1)
+  //   v2 <- EqualityCompare(v1 == v0)
+  //   if v2 == true then B4 else B3
+  // B3:
+  //   goto B2
+  // B4:
+  //   Return(v1)
+
+  PhiInstr* v1;
+  ConstantInstr* v0 = H.IntConstant(0);
+  auto b1 = H.flow_graph()->graph_entry()->normal_entry();
+  auto b2 = H.JoinEntry();
+  auto b3 = H.TargetEntry();
+  auto b4 = H.TargetEntry();
+
+  {
+    BlockBuilder builder(H.flow_graph(), b1);
+    builder.AddInstruction(new GotoInstr(b2, S.GetNextDeoptId()));
+  }
+
+  {
+    BlockBuilder builder(H.flow_graph(), b2);
+    v1 = H.Phi(b2, {{b1, v0}, {b3, FlowGraphBuilderHelper::kPhiSelfReference}});
+    builder.AddPhi(v1);
+    auto v2 = builder.AddDefinition(new EqualityCompareInstr(
+        TokenPosition::kNoSource, Token::kEQ, new Value(v1), new Value(v0),
+        kSmiCid, S.GetNextDeoptId()));
+    builder.AddBranch(
+        new StrictCompareInstr(
+            TokenPosition::kNoSource, Token::kEQ_STRICT, new Value(v2),
+            new Value(H.flow_graph()->GetConstant(Bool::True())),
+            /*needs_number_check=*/false, S.GetNextDeoptId()),
+        b4, b3);
+  }
+
+  {
+    BlockBuilder builder(H.flow_graph(), b3);
+    builder.AddInstruction(new GotoInstr(b2, S.GetNextDeoptId()));
+  }
+
+  {
+    BlockBuilder builder(H.flow_graph(), b4);
+    builder.AddReturn(new Value(v1));
+  }
+
+  H.FinishGraph();
+
+  // Graph transformations will attempt to copy deopt information from
+  // branches and block entries which we did not assign.
+  // To disable copying we mark graph to disable LICM.
+  H.flow_graph()->disallow_licm();
+
+  ConstantPropagator::Optimize(H.flow_graph());
+
+  auto& blocks = H.flow_graph()->reverse_postorder();
+  EXPECT_EQ(2, blocks.length());
+  EXPECT_PROPERTY(blocks[0], it.IsGraphEntry());
+  EXPECT_PROPERTY(blocks[1], it.IsFunctionEntry());
+  EXPECT_PROPERTY(blocks[1]->next(), it.IsReturn());
+  EXPECT_PROPERTY(blocks[1]->next()->AsReturn(),
+                  it.value()->definition() == v0);
+}
+
+}  // namespace dart
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc
index 92c0a64..f9f22ba 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc
@@ -761,7 +761,7 @@
   // the pool index at runtime. There is therefore no reason to put the name
   // into the pool in the first place.
   // TODO(dartbug.com/40605): Move this info to the pc descriptors.
-  if (!FLAG_dwarf_stack_traces) {
+  if (!FLAG_dwarf_stack_traces_mode) {
     const intptr_t name_index =
         assembler()->object_pool_builder().FindObject(name);
     code_source_map_builder_->NoteNullCheck(assembler()->CodeSize(), token_pos,
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc b/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
index 2a45dcb..2d5388c 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
@@ -7,6 +7,7 @@
 
 #include "vm/compiler/backend/flow_graph_compiler.h"
 
+#include "vm/compiler/api/type_check_mode.h"
 #include "vm/compiler/backend/il_printer.h"
 #include "vm/compiler/backend/locations.h"
 #include "vm/compiler/jit/compiler.h"
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc b/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
index 353cc31..051740f 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
@@ -7,6 +7,7 @@
 
 #include "vm/compiler/backend/flow_graph_compiler.h"
 
+#include "vm/compiler/api/type_check_mode.h"
 #include "vm/compiler/backend/il_printer.h"
 #include "vm/compiler/backend/locations.h"
 #include "vm/compiler/jit/compiler.h"
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc b/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
index 086ea4d..d982292 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
@@ -8,6 +8,7 @@
 #include "vm/compiler/backend/flow_graph_compiler.h"
 
 #include "vm/code_patcher.h"
+#include "vm/compiler/api/type_check_mode.h"
 #include "vm/compiler/backend/il_printer.h"
 #include "vm/compiler/backend/locations.h"
 #include "vm/compiler/frontend/flow_graph_builder.h"
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc b/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
index e9e7b22..cade733 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
@@ -7,6 +7,7 @@
 
 #include "vm/compiler/backend/flow_graph_compiler.h"
 
+#include "vm/compiler/api/type_check_mode.h"
 #include "vm/compiler/backend/il_printer.h"
 #include "vm/compiler/backend/locations.h"
 #include "vm/compiler/ffi/native_location.h"
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index c727de1..e635815 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -13,6 +13,7 @@
 #include "vm/compiler/backend/compile_type.h"
 #include "vm/compiler/backend/locations.h"
 #include "vm/compiler/backend/slot.h"
+#include "vm/compiler/compiler_pass.h"
 #include "vm/compiler/compiler_state.h"
 #include "vm/compiler/ffi/marshaller.h"
 #include "vm/compiler/ffi/native_calling_convention.h"
@@ -768,7 +769,6 @@
 
   explicit Instruction(intptr_t deopt_id = DeoptId::kNone)
       : deopt_id_(deopt_id),
-        lifetime_position_(kNoPlaceId),
         previous_(NULL),
         next_(NULL),
         env_(NULL),
@@ -963,8 +963,21 @@
   void RemoveEnvironment();
   void ReplaceInEnvironment(Definition* current, Definition* replacement);
 
-  intptr_t lifetime_position() const { return lifetime_position_; }
-  void set_lifetime_position(intptr_t pos) { lifetime_position_ = pos; }
+  // Different compiler passes can assign pass specific ids to the instruction.
+  // Only one id can be stored at a time.
+  intptr_t GetPassSpecificId(CompilerPass::Id pass) const {
+    return (PassSpecificId::DecodePass(pass_specific_id_) == pass)
+               ? PassSpecificId::DecodeId(pass_specific_id_)
+               : PassSpecificId::kNoId;
+  }
+  void SetPassSpecificId(CompilerPass::Id pass, intptr_t id) {
+    pass_specific_id_ = PassSpecificId::Encode(pass, id);
+  }
+  bool HasPassSpecificId(CompilerPass::Id pass) const {
+    return (PassSpecificId::DecodePass(pass_specific_id_) == pass) &&
+           (PassSpecificId::DecodeId(pass_specific_id_) !=
+            PassSpecificId::kNoId);
+  }
 
   bool HasUnmatchedInputRepresentations() const;
 
@@ -1039,11 +1052,6 @@
   // Get the block entry for this instruction.
   virtual BlockEntryInstr* GetBlock();
 
-  // Place identifiers used by the load optimization pass.
-  intptr_t place_id() const { return place_id_; }
-  void set_place_id(intptr_t place_id) { place_id_ = place_id; }
-  bool HasPlaceId() const { return place_id_ != kNoPlaceId; }
-
   intptr_t inlining_id() const { return inlining_id_; }
   void set_inlining_id(intptr_t value) {
     ASSERT(value >= 0);
@@ -1141,13 +1149,28 @@
 
   virtual void RawSetInputAt(intptr_t i, Value* value) = 0;
 
-  enum { kNoPlaceId = -1 };
+  class PassSpecificId {
+   public:
+    static intptr_t Encode(CompilerPass::Id pass, intptr_t id) {
+      return (id << kPassBits) | pass;
+    }
+
+    static CompilerPass::Id DecodePass(intptr_t value) {
+      return static_cast<CompilerPass::Id>(value & Utils::NBitMask(kPassBits));
+    }
+
+    static intptr_t DecodeId(intptr_t value) { return (value >> kPassBits); }
+
+    static constexpr intptr_t kNoId = -1;
+
+   private:
+    static constexpr intptr_t kPassBits = 8;
+    static_assert(CompilerPass::kNumPasses <= (1 << kPassBits),
+                  "Pass Id does not fit into the bit field");
+  };
 
   intptr_t deopt_id_;
-  union {
-    intptr_t lifetime_position_;  // Position used by register allocator.
-    intptr_t place_id_;
-  };
+  intptr_t pass_specific_id_ = PassSpecificId::kNoId;
   Instruction* previous_;
   Instruction* next_;
   Environment* env_;
@@ -5804,13 +5827,8 @@
   virtual bool HasUnknownSideEffects() const { return false; }
 
   virtual bool WillAllocateNewOrRemembered() const {
-    return WillAllocateNewOrRemembered(num_context_variables_);
-  }
-
-  static bool WillAllocateNewOrRemembered(intptr_t num_context_variables) {
-    if (!Context::IsValidLength(num_context_variables)) return false;
-    return Heap::IsAllocatableInNewSpace(
-        Context::InstanceSize(num_context_variables));
+    return compiler::target::WillAllocateNewOrRememberedContext(
+        num_context_variables_);
   }
 
   virtual AliasIdentity Identity() const { return identity_; }
@@ -5965,12 +5983,8 @@
     if (!num_elements()->BindsToConstant()) return false;
     const Object& length = num_elements()->BoundConstant();
     if (!length.IsSmi()) return false;
-    return WillAllocateNewOrRemembered(Smi::Cast(length).Value());
-  }
-
-  static bool WillAllocateNewOrRemembered(const intptr_t length) {
-    if (!Array::IsValidLength(length)) return false;
-    return !Array::UseCardMarkingForAllocation(length);
+    return compiler::target::WillAllocateNewOrRememberedArray(
+        Smi::Cast(length).Value());
   }
 
  private:
@@ -6291,13 +6305,8 @@
   virtual bool HasUnknownSideEffects() const { return false; }
 
   virtual bool WillAllocateNewOrRemembered() const {
-    return WillAllocateNewOrRemembered(context_slots().length());
-  }
-
-  static bool WillAllocateNewOrRemembered(intptr_t num_context_variables) {
-    if (!Context::IsValidLength(num_context_variables)) return false;
-    return Heap::IsAllocatableInNewSpace(
-        Context::InstanceSize(num_context_variables));
+    return compiler::target::WillAllocateNewOrRememberedContext(
+        context_slots().length());
   }
 
   PRINT_OPERANDS_TO_SUPPORT
diff --git a/runtime/vm/compiler/backend/il_deserializer.cc b/runtime/vm/compiler/backend/il_deserializer.cc
index 9901da4..c8d0916 100644
--- a/runtime/vm/compiler/backend/il_deserializer.cc
+++ b/runtime/vm/compiler/backend/il_deserializer.cc
@@ -800,10 +800,6 @@
 
   if (inst == nullptr) return nullptr;
   if (env != nullptr) env->DeepCopyTo(zone(), inst);
-  if (auto const lifetime_sexp =
-          CheckInteger(list->ExtraLookupValue("lifetime_position"))) {
-    inst->set_lifetime_position(lifetime_sexp->value());
-  }
   return inst;
 }
 
diff --git a/runtime/vm/compiler/backend/il_printer.cc b/runtime/vm/compiler/backend/il_printer.cc
index 7903b2e..e036600 100644
--- a/runtime/vm/compiler/backend/il_printer.cc
+++ b/runtime/vm/compiler/backend/il_printer.cc
@@ -4,7 +4,9 @@
 
 #include "vm/compiler/backend/il_printer.h"
 
+#include "vm/compiler/api/print_filter.h"
 #include "vm/compiler/backend/il.h"
+#include "vm/compiler/backend/linearscan.h"
 #include "vm/compiler/backend/range_analysis.h"
 #include "vm/compiler/ffi/native_calling_convention.h"
 #include "vm/os.h"
@@ -14,60 +16,6 @@
 
 #if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
 
-DEFINE_FLAG(charp,
-            print_flow_graph_filter,
-            NULL,
-            "Print only IR of functions with matching names");
-
-// Checks whether function's name matches the given filter, which is
-// a comma-separated list of strings.
-bool FlowGraphPrinter::PassesFilter(const char* filter,
-                                    const Function& function) {
-  if (filter == NULL) {
-    return true;
-  }
-
-  char* save_ptr;  // Needed for strtok_r.
-  const char* scrubbed_name =
-      String::Handle(function.QualifiedScrubbedName()).ToCString();
-  const char* function_name = function.ToFullyQualifiedCString();
-  intptr_t function_name_len = strlen(function_name);
-
-  intptr_t len = strlen(filter) + 1;  // Length with \0.
-  char* filter_buffer = new char[len];
-  strncpy(filter_buffer, filter, len);  // strtok modifies arg 1.
-  char* token = strtok_r(filter_buffer, ",", &save_ptr);
-  bool found = false;
-  while (token != NULL) {
-    if ((strstr(function_name, token) != NULL) ||
-        (strstr(scrubbed_name, token) != NULL)) {
-      found = true;
-      break;
-    }
-    const intptr_t token_len = strlen(token);
-    if (token[token_len - 1] == '%') {
-      if (function_name_len > token_len) {
-        const char* suffix =
-            function_name + (function_name_len - token_len + 1);
-        if (strncmp(suffix, token, token_len - 1) == 0) {
-          found = true;
-          break;
-        }
-      }
-    }
-    token = strtok_r(NULL, ",", &save_ptr);
-  }
-  delete[] filter_buffer;
-
-  return found;
-}
-
-bool FlowGraphPrinter::ShouldPrint(const Function& function) {
-  return PassesFilter(FLAG_print_flow_graph_filter, function);
-}
-
-#if !defined(DART_PRECOMPILED_RUNTIME)
-
 DEFINE_FLAG(bool,
             display_sorted_ic_data,
             false,
@@ -76,6 +24,10 @@
 
 DECLARE_FLAG(bool, trace_inlining_intervals);
 
+bool FlowGraphPrinter::ShouldPrint(const Function& function) {
+  return compiler::PrintFilter::ShouldPrint(function);
+}
+
 void FlowGraphPrinter::PrintGraph(const char* phase, FlowGraph* flow_graph) {
   LogBlock lb;
   THR_Print("*** BEGIN CFG\n%s\n", phase);
@@ -123,8 +75,8 @@
   if (print_locations && (instr->HasLocs())) {
     instr->locs()->PrintTo(&f);
   }
-  if (instr->lifetime_position() != -1) {
-    THR_Print("%3" Pd ": ", instr->lifetime_position());
+  if (FlowGraphAllocator::HasLifetimePosition(instr)) {
+    THR_Print("%3" Pd ": ", FlowGraphAllocator::GetLifetimePosition(instr));
   }
   if (!instr->IsBlockEntry()) THR_Print("    ");
   THR_Print("%s", str);
@@ -1224,12 +1176,8 @@
   return Thread::Current()->zone()->MakeCopyOfString(buffer);
 }
 
-#endif  // !defined(DART_PRECOMPILED_RUNTIME)
-
 #else  // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
 
-#if !defined(DART_PRECOMPILED_RUNTIME)
-
 const char* Instruction::ToCString() const {
   return DebugName();
 }
@@ -1266,8 +1214,6 @@
   return false;
 }
 
-#endif  // !defined(DART_PRECOMPILED_RUNTIME)
-
 #endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
 
 }  // namespace dart
diff --git a/runtime/vm/compiler/backend/il_printer.h b/runtime/vm/compiler/backend/il_printer.h
index 32cc84a..becdf59 100644
--- a/runtime/vm/compiler/backend/il_printer.h
+++ b/runtime/vm/compiler/backend/il_printer.h
@@ -54,8 +54,6 @@
 
   static bool ShouldPrint(const Function& function);
 
-  static bool PassesFilter(const char* filter, const Function& function);
-
  private:
   const Function& function_;
   const GrowableArray<BlockEntryInstr*>& block_order_;
diff --git a/runtime/vm/compiler/backend/il_serializer.cc b/runtime/vm/compiler/backend/il_serializer.cc
index f2e5cf9..bdd8812 100644
--- a/runtime/vm/compiler/backend/il_serializer.cc
+++ b/runtime/vm/compiler/backend/il_serializer.cc
@@ -835,9 +835,6 @@
   if (!token_pos().IsNoSource()) {
     s->AddExtraInteger(sexp, "token_pos", token_pos().value());
   }
-  if (lifetime_position() != kNoPlaceId) {
-    s->AddExtraInteger(sexp, "lifetime_position", lifetime_position());
-  }
 }
 
 SExpression* Range::ToSExpression(FlowGraphSerializer* s) {
diff --git a/runtime/vm/compiler/backend/il_test_helper.h b/runtime/vm/compiler/backend/il_test_helper.h
index 9655791..5232aabb 100644
--- a/runtime/vm/compiler/backend/il_test_helper.h
+++ b/runtime/vm/compiler/backend/il_test_helper.h
@@ -253,17 +253,20 @@
     return flow_graph_.GetConstant(Double::Handle(Double::NewCanonical(value)));
   }
 
+  static constexpr Definition* kPhiSelfReference = nullptr;
+
   PhiInstr* Phi(JoinEntryInstr* join,
                 std::initializer_list<std::pair<BlockEntryInstr*, Definition*>>
-                    incomming) {
-    auto phi = new PhiInstr(join, incomming.size());
-    for (size_t i = 0; i < incomming.size(); i++) {
+                    incoming) {
+    auto phi = new PhiInstr(join, incoming.size());
+    for (size_t i = 0; i < incoming.size(); i++) {
       auto input = new Value(flow_graph_.constant_dead());
       phi->SetInputAt(i, input);
       input->definition()->AddInputUse(input);
     }
-    for (auto pair : incomming) {
-      pending_phis_.Add({phi, pair.first, pair.second});
+    for (auto pair : incoming) {
+      pending_phis_.Add({phi, pair.first,
+                         pair.second == kPhiSelfReference ? phi : pair.second});
     }
     return phi;
   }
diff --git a/runtime/vm/compiler/backend/linearscan.cc b/runtime/vm/compiler/backend/linearscan.cc
index 402954d..9bcd928 100644
--- a/runtime/vm/compiler/backend/linearscan.cc
+++ b/runtime/vm/compiler/backend/linearscan.cc
@@ -682,15 +682,15 @@
       range->set_assigned_location(loc);
       AssignSafepoints(defn, range);
       range->finger()->Initialize(range);
-      SplitInitialDefinitionAt(range, block->lifetime_position() + 1);
+      SplitInitialDefinitionAt(range, GetLifetimePosition(block) + 1);
       ConvertAllUses(range);
 
       // We have exception/stacktrace in a register and need to
       // ensure this register is not available for register allocation during
       // the [CatchBlockEntry] to ensure it's not overwritten.
       if (loc.IsRegister()) {
-        BlockLocation(loc, block->lifetime_position(),
-                      block->lifetime_position() + 1);
+        BlockLocation(loc, GetLifetimePosition(block),
+                      GetLifetimePosition(block) + 1);
       }
       return;
     }
@@ -745,12 +745,12 @@
     range->set_assigned_location(loc);
     if (loc.IsRegister()) {
       AssignSafepoints(defn, range);
-      if (range->End() > (block->lifetime_position() + 2)) {
-        SplitInitialDefinitionAt(range, block->lifetime_position() + 2);
+      if (range->End() > (GetLifetimePosition(block) + 2)) {
+        SplitInitialDefinitionAt(range, GetLifetimePosition(block) + 2);
       }
       ConvertAllUses(range);
-      BlockLocation(loc, block->lifetime_position(),
-                    block->lifetime_position() + 2);
+      BlockLocation(loc, GetLifetimePosition(block),
+                    GetLifetimePosition(block) + 2);
       return;
     }
   } else {
@@ -837,7 +837,7 @@
   ParallelMoveInstr* parallel_move = goto_instr->parallel_move();
 
   // All uses are recorded at the position of parallel move preceding goto.
-  const intptr_t pos = goto_instr->lifetime_position();
+  const intptr_t pos = GetLifetimePosition(goto_instr);
 
   JoinEntryInstr* join = goto_instr->successor();
   ASSERT(join != NULL);
@@ -975,7 +975,7 @@
     }
 
     const intptr_t block_start_pos = block->start_pos();
-    const intptr_t use_pos = current->lifetime_position() + 1;
+    const intptr_t use_pos = GetLifetimePosition(current) + 1;
 
     Location* locations = flow_graph_.zone()->Alloc<Location>(env->Length());
 
@@ -1306,7 +1306,7 @@
     }
   }
 
-  const intptr_t pos = current->lifetime_position();
+  const intptr_t pos = GetLifetimePosition(current);
   ASSERT(IsInstructionStartPosition(pos));
 
   ASSERT(locs->input_count() == current->InputCount());
@@ -1524,11 +1524,12 @@
   ASSERT(pos > 0);
   Instruction* prev = instr->previous();
   ParallelMoveInstr* move = prev->AsParallelMove();
-  if ((move == NULL) || (move->lifetime_position() != pos)) {
+  if ((move == NULL) ||
+      (FlowGraphAllocator::GetLifetimePosition(move) != pos)) {
     move = new ParallelMoveInstr();
     prev->LinkTo(move);
     move->LinkTo(instr);
-    move->set_lifetime_position(pos);
+    FlowGraphAllocator::SetLifetimePosition(move, pos);
   }
   return move;
 }
@@ -1536,7 +1537,8 @@
 static ParallelMoveInstr* CreateParallelMoveAfter(Instruction* instr,
                                                   intptr_t pos) {
   Instruction* next = instr->next();
-  if (next->IsParallelMove() && (next->lifetime_position() == pos)) {
+  if (next->IsParallelMove() &&
+      (FlowGraphAllocator::GetLifetimePosition(next) == pos)) {
     return next->AsParallelMove();
   }
   return CreateParallelMoveBefore(next, pos);
@@ -1558,7 +1560,7 @@
     instructions_.Add(block);
     block_entries_.Add(block);
     block->set_start_pos(pos);
-    block->set_lifetime_position(pos);
+    SetLifetimePosition(block, pos);
     pos += 2;
 
     for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
@@ -1567,7 +1569,7 @@
       if (!current->IsParallelMove()) {
         instructions_.Add(current);
         block_entries_.Add(block);
-        current->set_lifetime_position(pos);
+        SetLifetimePosition(current, pos);
         pos += 2;
       }
     }
@@ -1832,7 +1834,7 @@
   BlockEntryInstr* split_block_entry = BlockEntryAt(to);
   ASSERT(split_block_entry == InstructionAt(to)->GetBlock());
 
-  if (from < split_block_entry->lifetime_position()) {
+  if (from < GetLifetimePosition(split_block_entry)) {
     // Interval [from, to) spans multiple blocks.
 
     // If the last block is inside a loop, prefer splitting at the outermost
@@ -1863,16 +1865,16 @@
       }
     }
     while ((loop_info != nullptr) &&
-           (from < loop_info->header()->lifetime_position())) {
+           (from < GetLifetimePosition(loop_info->header()))) {
       split_block_entry = loop_info->header();
       loop_info = loop_info->outer();
       TRACE_ALLOC(THR_Print("  move back to loop header B%" Pd " at %" Pd "\n",
                             split_block_entry->block_id(),
-                            split_block_entry->lifetime_position()));
+                            GetLifetimePosition(split_block_entry)));
     }
 
     // Split at block's start.
-    split_pos = split_block_entry->lifetime_position();
+    split_pos = GetLifetimePosition(split_block_entry);
   } else {
     // Interval [from, to) is contained inside a single block.
 
@@ -2587,7 +2589,7 @@
       continue;
     }
 
-    const intptr_t pos = safepoint_instr->lifetime_position();
+    const intptr_t pos = GetLifetimePosition(safepoint_instr);
     if (range->End() <= pos) break;
 
     if (range->Contains(pos)) {
diff --git a/runtime/vm/compiler/backend/linearscan.h b/runtime/vm/compiler/backend/linearscan.h
index 541bd2c..4bb868b 100644
--- a/runtime/vm/compiler/backend/linearscan.h
+++ b/runtime/vm/compiler/backend/linearscan.h
@@ -63,6 +63,20 @@
   // Map a virtual register number to its live range.
   LiveRange* GetLiveRange(intptr_t vreg);
 
+  DART_FORCE_INLINE static void SetLifetimePosition(Instruction* instr,
+                                                    intptr_t pos) {
+    instr->SetPassSpecificId(CompilerPass::kAllocateRegisters, pos);
+  }
+
+  DART_FORCE_INLINE static bool HasLifetimePosition(Instruction* instr) {
+    return instr->HasPassSpecificId(CompilerPass::kAllocateRegisters);
+  }
+
+  DART_FORCE_INLINE static intptr_t GetLifetimePosition(
+      const Instruction* instr) {
+    return instr->GetPassSpecificId(CompilerPass::kAllocateRegisters);
+  }
+
  private:
   void CollectRepresentations();
 
diff --git a/runtime/vm/compiler/backend/redundancy_elimination.cc b/runtime/vm/compiler/backend/redundancy_elimination.cc
index 0559662..01e5c51 100644
--- a/runtime/vm/compiler/backend/redundancy_elimination.cc
+++ b/runtime/vm/compiler/backend/redundancy_elimination.cc
@@ -1245,6 +1245,18 @@
   return phi_moves;
 }
 
+DART_FORCE_INLINE static void SetPlaceId(Instruction* instr, intptr_t id) {
+  instr->SetPassSpecificId(CompilerPass::kCSE, id);
+}
+
+DART_FORCE_INLINE static intptr_t GetPlaceId(const Instruction* instr) {
+  return instr->GetPassSpecificId(CompilerPass::kCSE);
+}
+
+DART_FORCE_INLINE static bool HasPlaceId(const Instruction* instr) {
+  return instr->HasPassSpecificId(CompilerPass::kCSE);
+}
+
 enum CSEMode { kOptimizeLoads, kOptimizeStores };
 
 static AliasedSet* NumberPlaces(
@@ -1282,7 +1294,7 @@
         }
       }
 
-      instr->set_place_id(result->id());
+      SetPlaceId(instr, result->id());
     }
   }
 
@@ -1309,8 +1321,8 @@
                                 intptr_t loop_header_index,
                                 Instruction* instr) {
   return IsLoadEliminationCandidate(instr) && (sets != NULL) &&
-         instr->HasPlaceId() && ((*sets)[loop_header_index] != NULL) &&
-         (*sets)[loop_header_index]->Contains(instr->place_id());
+         HasPlaceId(instr) &&
+         (*sets)[loop_header_index]->Contains(GetPlaceId(instr));
 }
 
 LICM::LICM(FlowGraph* flow_graph) : flow_graph_(flow_graph) {
@@ -1659,7 +1671,7 @@
             // instruction that still points to the old place with a more
             // generic alias.
             const intptr_t old_alias_id = aliased_set_->LookupAliasId(
-                aliased_set_->places()[instr->place_id()]->ToAlias());
+                aliased_set_->places()[GetPlaceId(instr)]->ToAlias());
             killed = aliased_set_->GetKilledSet(old_alias_id);
           }
 
@@ -1678,7 +1690,7 @@
                   if (FLAG_trace_optimization) {
                     THR_Print("Removing redundant store to place %" Pd
                               " in block B%" Pd "\n",
-                              instr->place_id(), block->block_id());
+                              GetPlaceId(instr), block->block_id());
                   }
                   instr_it.RemoveCurrentFromGraph();
                   continue;
@@ -1711,8 +1723,8 @@
           // load forwarding.
           const Place* canonical = aliased_set_->LookupCanonical(&place);
           if ((canonical != NULL) &&
-              (canonical->id() != instr->AsDefinition()->place_id())) {
-            instr->AsDefinition()->set_place_id(canonical->id());
+              (canonical->id() != GetPlaceId(instr->AsDefinition()))) {
+            SetPlaceId(instr->AsDefinition(), canonical->id());
           }
         }
 
@@ -1751,11 +1763,11 @@
             intptr_t place_id = 0;
             if (auto load = use->instruction()->AsLoadField()) {
               slot = &load->slot();
-              place_id = load->place_id();
+              place_id = GetPlaceId(load);
             } else if (auto store =
                            use->instruction()->AsStoreInstanceField()) {
               slot = &store->slot();
-              place_id = store->place_id();
+              place_id = GetPlaceId(store);
             }
 
             if (slot != nullptr) {
@@ -1789,7 +1801,7 @@
           continue;
         }
 
-        const intptr_t place_id = defn->place_id();
+        const intptr_t place_id = GetPlaceId(defn);
         if (gen->Contains(place_id)) {
           // This is a locally redundant load.
           ASSERT((out_values != NULL) && ((*out_values)[place_id] != NULL));
@@ -1965,7 +1977,7 @@
               (in_[preorder_number]->Contains(place_id))) {
             PhiInstr* phi = new (Z)
                 PhiInstr(block->AsJoinEntry(), block->PredecessorCount());
-            phi->set_place_id(place_id);
+            SetPlaceId(phi, place_id);
             pending_phis.Add(phi);
             in_value = phi;
           }
@@ -2103,14 +2115,14 @@
     // Incoming values are different. Phi is required to merge.
     PhiInstr* phi =
         new (Z) PhiInstr(block->AsJoinEntry(), block->PredecessorCount());
-    phi->set_place_id(place_id);
+    SetPlaceId(phi, place_id);
     FillPhiInputs(phi);
     return phi;
   }
 
   void FillPhiInputs(PhiInstr* phi) {
     BlockEntryInstr* block = phi->GetBlock();
-    const intptr_t place_id = phi->place_id();
+    const intptr_t place_id = GetPlaceId(phi);
 
     for (intptr_t i = 0; i < block->PredecessorCount(); i++) {
       BlockEntryInstr* pred = block->PredecessorAt(i);
@@ -2154,9 +2166,9 @@
 
       for (intptr_t i = 0; i < loads->length(); i++) {
         Definition* load = (*loads)[i];
-        if (!in->Contains(load->place_id())) continue;  // No incoming value.
+        if (!in->Contains(GetPlaceId(load))) continue;  // No incoming value.
 
-        Definition* replacement = MergeIncomingValues(block, load->place_id());
+        Definition* replacement = MergeIncomingValues(block, GetPlaceId(load));
         ASSERT(replacement != NULL);
 
         // Sets of outgoing values are not linked into use lists so
@@ -2606,17 +2618,17 @@
 
         // Handle stores.
         if (is_store) {
-          if (kill->Contains(instr->place_id())) {
-            if (!live_in->Contains(instr->place_id()) &&
+          if (kill->Contains(GetPlaceId(instr))) {
+            if (!live_in->Contains(GetPlaceId(instr)) &&
                 CanEliminateStore(instr)) {
               if (FLAG_trace_optimization) {
                 THR_Print("Removing dead store to place %" Pd " in block B%" Pd
                           "\n",
-                          instr->place_id(), block->block_id());
+                          GetPlaceId(instr), block->block_id());
               }
               instr_it.RemoveCurrentFromGraph();
             }
-          } else if (!live_in->Contains(instr->place_id())) {
+          } else if (!live_in->Contains(GetPlaceId(instr))) {
             // Mark this store as down-ward exposed: They are the only
             // candidates for the global store elimination.
             if (exposed_stores == NULL) {
@@ -2628,8 +2640,8 @@
             exposed_stores->Add(instr);
           }
           // Interfering stores kill only loads from the same place.
-          kill->Add(instr->place_id());
-          live_in->Remove(instr->place_id());
+          kill->Add(GetPlaceId(instr));
+          live_in->Remove(GetPlaceId(instr));
           continue;
         }
 
@@ -2690,11 +2702,11 @@
         }
         // Eliminate a downward exposed store if the corresponding place is not
         // in live-out.
-        if (!live_out->Contains(instr->place_id()) &&
+        if (!live_out->Contains(GetPlaceId(instr)) &&
             CanEliminateStore(instr)) {
           if (FLAG_trace_optimization) {
             THR_Print("Removing dead store to place %" Pd " block B%" Pd "\n",
-                      instr->place_id(), block->block_id());
+                      GetPlaceId(instr), block->block_id());
           }
           instr->RemoveFromGraph(/* ignored */ false);
         }
@@ -3316,6 +3328,18 @@
     EliminateDeadParameters();
   }
 
+  static intptr_t GetParameterId(const Instruction* instr) {
+    return instr->GetPassSpecificId(CompilerPass::kTryCatchOptimization);
+  }
+
+  static void SetParameterId(Instruction* instr, intptr_t id) {
+    instr->SetPassSpecificId(CompilerPass::kTryCatchOptimization, id);
+  }
+
+  static bool HasParameterId(Instruction* instr) {
+    return instr->HasPassSpecificId(CompilerPass::kTryCatchOptimization);
+  }
+
   // Assign sequential ids to each ParameterInstr in each CatchEntryBlock.
   // Collect reverse mapping from try indexes to corresponding catches.
   void NumberCatchEntryParameters() {
@@ -3324,7 +3348,7 @@
           *catch_entry->initial_definitions();
       for (auto idef : idefs) {
         if (idef->IsParameter()) {
-          idef->set_place_id(parameter_info_.length());
+          SetParameterId(idef, parameter_info_.length());
           parameter_info_.Add(new ParameterInfo(idef->AsParameter()));
         }
       }
@@ -3363,14 +3387,14 @@
             // already present in the list.
             bool found = false;
             for (auto other_defn :
-                 parameter_info_[param->place_id()]->incoming) {
+                 parameter_info_[GetParameterId(param)]->incoming) {
               if (other_defn == defn) {
                 found = true;
                 break;
               }
             }
             if (!found) {
-              parameter_info_[param->place_id()]->incoming.Add(defn);
+              parameter_info_[GetParameterId(param)]->incoming.Add(defn);
             }
           }
         }
@@ -3410,7 +3434,7 @@
     while (!worklist_.IsEmpty()) {
       Definition* defn = worklist_.RemoveLast();
       if (ParameterInstr* param = defn->AsParameter()) {
-        auto s = parameter_info_[param->place_id()];
+        auto s = parameter_info_[GetParameterId(param)];
         for (auto input : s->incoming) {
           MarkLive(input);
         }
@@ -3431,8 +3455,8 @@
         worklist_.Add(phi);
       }
     } else if (ParameterInstr* param = defn->AsParameter()) {
-      if (param->place_id() != -1) {
-        auto input_s = parameter_info_[param->place_id()];
+      if (HasParameterId(param)) {
+        auto input_s = parameter_info_[GetParameterId(param)];
         if (!input_s->alive) {
           input_s->alive = true;
           worklist_.Add(param);
@@ -3466,7 +3490,7 @@
 
         for (intptr_t env_idx = 0; env_idx < idefs.length(); ++env_idx) {
           if (ParameterInstr* param = idefs[env_idx]->AsParameter()) {
-            if (!parameter_info_[param->place_id()]->alive) {
+            if (!parameter_info_[GetParameterId(param)]->alive) {
               env->ValueAt(env_idx)->BindToEnvironment(
                   flow_graph_->constant_null());
             }
diff --git a/runtime/vm/compiler/compiler_pass.h b/runtime/vm/compiler/compiler_pass.h
index f3333b91..a5d4cdd 100644
--- a/runtime/vm/compiler/compiler_pass.h
+++ b/runtime/vm/compiler/compiler_pass.h
@@ -115,7 +115,7 @@
   };
 
 #define ADD_ONE(name) +1
-  static const intptr_t kNumPasses = 0 COMPILER_PASS_LIST(ADD_ONE);
+  static constexpr intptr_t kNumPasses = 0 COMPILER_PASS_LIST(ADD_ONE);
 #undef ADD_ONE
 
   CompilerPass(Id id, const char* name) : name_(name), flags_(0) {
diff --git a/runtime/vm/compiler/compiler_sources.gni b/runtime/vm/compiler/compiler_sources.gni
index fe2e471..6b59f94 100644
--- a/runtime/vm/compiler/compiler_sources.gni
+++ b/runtime/vm/compiler/compiler_sources.gni
@@ -28,13 +28,6 @@
   "assembler/assembler_ia32.h",
   "assembler/assembler_x64.cc",
   "assembler/assembler_x64.h",
-  "assembler/disassembler.cc",
-  "assembler/disassembler.h",
-  "assembler/disassembler_arm.cc",
-  "assembler/disassembler_arm64.cc",
-  "assembler/disassembler_kbc.cc",
-  "assembler/disassembler_kbc.h",
-  "assembler/disassembler_x86.cc",
   "assembler/object_pool_builder.h",
   "backend/block_builder.h",
   "backend/block_scheduler.cc",
@@ -112,8 +105,6 @@
   "ffi/native_calling_convention.h",
   "ffi/native_location.cc",
   "ffi/native_location.h",
-  "ffi/native_type.cc",
-  "ffi/native_type.h",
   "ffi/recognized_method.cc",
   "ffi/recognized_method.h",
   "frontend/base_flow_graph_builder.cc",
@@ -150,8 +141,6 @@
   "graph_intrinsifier_x64.cc",
   "intrinsifier.cc",
   "intrinsifier.h",
-  "jit/compiler.cc",
-  "jit/compiler.h",
   "jit/jit_call_specializer.cc",
   "jit/jit_call_specializer.h",
   "method_recognizer.cc",
@@ -159,8 +148,6 @@
   "recognized_methods_list.h",
   "relocation.cc",
   "relocation.h",
-  "runtime_api.cc",
-  "runtime_api.h",
   "stub_code_compiler.cc",
   "stub_code_compiler.h",
   "stub_code_compiler_arm.cc",
@@ -179,6 +166,7 @@
   "assembler/assembler_x64_test.cc",
   "assembler/disassembler_test.cc",
   "backend/bce_test.cc",
+  "backend/constant_propagator_test.cc",
   "backend/il_test.cc",
   "backend/il_test_helper.h",
   "backend/il_test_helper.cc",
@@ -196,3 +184,26 @@
   "cha_test.cc",
   "write_barrier_elimination_test.cc",
 ]
+
+compiler_api_sources = [
+  "api/deopt_id.h",
+  "api/print_filter.cc",
+  "api/print_filter.h",
+  "api/type_check_mode.h",
+  "ffi/native_type.cc",
+  "ffi/native_type.h",
+  "jit/compiler.cc",
+  "jit/compiler.h",
+  "runtime_api.cc",
+  "runtime_api.h",
+]
+
+disassembler_sources = [
+  "assembler/disassembler.cc",
+  "assembler/disassembler.h",
+  "assembler/disassembler_arm.cc",
+  "assembler/disassembler_arm64.cc",
+  "assembler/disassembler_kbc.cc",
+  "assembler/disassembler_kbc.h",
+  "assembler/disassembler_x86.cc",
+]
diff --git a/runtime/vm/compiler/compiler_state.cc b/runtime/vm/compiler/compiler_state.cc
index 47407f5..cdf77e6 100644
--- a/runtime/vm/compiler/compiler_state.cc
+++ b/runtime/vm/compiler/compiler_state.cc
@@ -3,12 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 
 #include "vm/compiler/compiler_state.h"
-#include "vm/growable_array.h"
-
-#ifndef DART_PRECOMPILED_RUNTIME
 
 #include <functional>
 
+#include "vm/compiler/backend/slot.h"
+#include "vm/growable_array.h"
 #include "vm/scopes.h"
 
 namespace dart {
@@ -72,5 +71,3 @@
 }
 
 }  // namespace dart
-
-#endif  // DART_PRECOMPILED_RUNTIME
diff --git a/runtime/vm/compiler/compiler_state.h b/runtime/vm/compiler/compiler_state.h
index 4b3a590..619f25b 100644
--- a/runtime/vm/compiler/compiler_state.h
+++ b/runtime/vm/compiler/compiler_state.h
@@ -5,6 +5,7 @@
 #ifndef RUNTIME_VM_COMPILER_COMPILER_STATE_H_
 #define RUNTIME_VM_COMPILER_COMPILER_STATE_H_
 
+#include "vm/compiler/api/deopt_id.h"
 #include "vm/compiler/cha.h"
 #include "vm/heap/safepoint.h"
 #include "vm/thread.h"
@@ -16,44 +17,6 @@
 class SlotCache;
 class Slot;
 
-// Deoptimization Id logic.
-//
-// Deoptimization ids are used to refer to deoptimization points, at which
-// control can enter unoptimized code from the optimized version of the code.
-//
-// Note: any instruction that does a call has two deoptimization points,
-// one before the call and one after the call - so that we could deoptimize
-// to either before or after the call depending on whether the same call
-// already occured in the optimized code (and potentially produced
-// observable side-effects) or not.
-//
-// To simplify implementation we always allocate two deopt ids (one for before
-// point and one for the after point).
-class DeoptId : public AllStatic {
- public:
-  static constexpr intptr_t kNone = -1;
-
-  static inline intptr_t Next(intptr_t deopt_id) { return deopt_id + kStep; }
-
-  static inline intptr_t ToDeoptAfter(intptr_t deopt_id) {
-    ASSERT(IsDeoptBefore(deopt_id));
-    return deopt_id + kAfterOffset;
-  }
-
-  static inline bool IsDeoptBefore(intptr_t deopt_id) {
-    return (deopt_id % kStep) == kBeforeOffset;
-  }
-
-  static inline bool IsDeoptAfter(intptr_t deopt_id) {
-    return (deopt_id % kStep) == kAfterOffset;
-  }
-
- private:
-  static constexpr intptr_t kStep = 2;
-  static constexpr intptr_t kBeforeOffset = 0;
-  static constexpr intptr_t kAfterOffset = 1;
-};
-
 // Global compiler state attached to the thread.
 class CompilerState : public ThreadStackResource {
  public:
diff --git a/runtime/vm/compiler/ffi/native_type.cc b/runtime/vm/compiler/ffi/native_type.cc
index e69380c..adcc57a 100644
--- a/runtime/vm/compiler/ffi/native_type.cc
+++ b/runtime/vm/compiler/ffi/native_type.cc
@@ -6,10 +6,13 @@
 
 #include "platform/assert.h"
 #include "platform/globals.h"
-#include "vm/compiler/backend/locations.h"
 #include "vm/compiler/runtime_api.h"
 #include "vm/object.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/backend/locations.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 namespace compiler {
@@ -121,6 +124,7 @@
   UNREACHABLE();
 }
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
 bool NativeFundamentalType::IsExpressibleAsRepresentation() const {
   switch (representation_) {
     case kInt8:
@@ -163,6 +167,7 @@
       UNREACHABLE();
   }
 }
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
 bool NativeFundamentalType::Equals(const NativeType& other) const {
   if (!other.IsFundamental()) {
@@ -242,6 +247,7 @@
   return NativeType::FromTypedDataClassId(type.type_class_id(), zone);
 }
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
 static FundamentalType fundamental_rep(Representation rep) {
   switch (rep) {
     case kUnboxedDouble:
@@ -264,6 +270,7 @@
                                                              Zone* zone) {
   return *new (zone) NativeFundamentalType(fundamental_rep(rep));
 }
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
 const char* NativeType::ToCString() const {
   char buffer[1024];
diff --git a/runtime/vm/compiler/ffi/native_type.h b/runtime/vm/compiler/ffi/native_type.h
index ddd6941..0eee4de 100644
--- a/runtime/vm/compiler/ffi/native_type.h
+++ b/runtime/vm/compiler/ffi/native_type.h
@@ -9,11 +9,16 @@
 
 #include "platform/assert.h"
 #include "vm/allocation.h"
-#include "vm/compiler/backend/locations.h"
 #include "vm/compiler/runtime_api.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/backend/locations.h"
+#endif
+
 namespace dart {
 
+class BufferFormatter;
+
 namespace compiler {
 
 namespace ffi {
@@ -31,7 +36,7 @@
 // * tagged
 // * untagged
 //
-// Instead, NativeTypes support representations not supprted in Dart's unboxed
+// Instead, NativeTypes support representations not supported in Dart's unboxed
 // Representations, such as:
 // * Fundamental types (https://en.cppreference.com/w/cpp/language/types):
 //   * int8_t
@@ -48,8 +53,11 @@
  public:
   static NativeType& FromAbstractType(const AbstractType& type, Zone* zone);
   static NativeType& FromTypedDataClassId(classid_t class_id, Zone* zone);
+
+#if !defined(DART_PRECOMPILED_RUNTIME)
   static NativeFundamentalType& FromUnboxedRepresentation(Representation rep,
                                                           Zone* zone);
+#endif
 
   virtual bool IsFundamental() const { return false; }
   const NativeFundamentalType& AsFundamental() const;
@@ -71,6 +79,7 @@
   // The alignment in bytes of this representation as member of a composite.
   virtual intptr_t AlignmentInBytesField() const = 0;
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
   // NativeTypes which are available as unboxed Representations.
   virtual bool IsExpressibleAsRepresentation() const { return false; }
 
@@ -82,6 +91,7 @@
     const auto& widened = WidenTo4Bytes(zone_);
     return widened.AsRepresentation();
   }
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
   virtual bool Equals(const NativeType& other) const { UNREACHABLE(); }
 
@@ -135,8 +145,10 @@
   virtual intptr_t AlignmentInBytesStack() const;
   virtual intptr_t AlignmentInBytesField() const;
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
   virtual bool IsExpressibleAsRepresentation() const;
   virtual Representation AsRepresentation() const;
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
   virtual bool Equals(const NativeType& other) const;
   virtual NativeFundamentalType& Split(intptr_t part, Zone* zone) const;
diff --git a/runtime/vm/compiler/jit/compiler.cc b/runtime/vm/compiler/jit/compiler.cc
index ea9e245..c8a3bf3 100644
--- a/runtime/vm/compiler/jit/compiler.cc
+++ b/runtime/vm/compiler/jit/compiler.cc
@@ -4,9 +4,9 @@
 
 #include "vm/compiler/jit/compiler.h"
 
-#include "vm/compiler/assembler/assembler.h"
-
+#if !defined(DART_PRECOMPILED_RUNTIME)
 #include "vm/code_patcher.h"
+#include "vm/compiler/assembler/assembler.h"
 #include "vm/compiler/assembler/disassembler.h"
 #include "vm/compiler/backend/block_scheduler.h"
 #include "vm/compiler/backend/branch_optimizer.h"
@@ -45,6 +45,7 @@
 #include "vm/thread_registry.h"
 #include "vm/timeline.h"
 #include "vm/timer.h"
+#endif
 
 namespace dart {
 
diff --git a/runtime/vm/compiler/jit/compiler.h b/runtime/vm/compiler/jit/compiler.h
index a810124..b859021 100644
--- a/runtime/vm/compiler/jit/compiler.h
+++ b/runtime/vm/compiler/jit/compiler.h
@@ -6,7 +6,7 @@
 #define RUNTIME_VM_COMPILER_JIT_COMPILER_H_
 
 #include "vm/allocation.h"
-#include "vm/compiler/compiler_state.h"
+#include "vm/compiler/api/deopt_id.h"
 #include "vm/growable_array.h"
 #include "vm/runtime_entry.h"
 #include "vm/thread_pool.h"
diff --git a/runtime/vm/compiler/relocation.cc b/runtime/vm/compiler/relocation.cc
index 2f56864..3835d55 100644
--- a/runtime/vm/compiler/relocation.cc
+++ b/runtime/vm/compiler/relocation.cc
@@ -133,17 +133,12 @@
         }
         num_calls++;
 
-        target_ = call.Get<Code::kSCallTableFunctionTarget>();
-        if (target_.IsFunction()) {
-          auto& fun = Function::Cast(target_);
-          ASSERT(fun.HasCode());
-          destination_ = fun.CurrentCode();
-          ASSERT(!destination_.IsStubCode());
-        } else {
-          target_ = call.Get<Code::kSCallTableCodeTarget>();
-          ASSERT(target_.IsCode());
-          destination_ = Code::Cast(target_).raw();
-        }
+        // The precompiler should have already replaced all function entries
+        // with code entries.
+        ASSERT(call.Get<Code::kSCallTableFunctionTarget>() == Function::null());
+        target_ = call.Get<Code::kSCallTableCodeTarget>();
+        ASSERT(target_.IsCode());
+        destination_ = Code::Cast(target_).raw();
 
         // A call site can decide to jump not to the beginning of a function but
         // rather jump into it at a certain (positive) offset.
@@ -256,17 +251,12 @@
       continue;
     }
 
-    target_ = call.Get<Code::kSCallTableFunctionTarget>();
-    if (target_.IsFunction()) {
-      auto& fun = Function::Cast(target_);
-      ASSERT(fun.HasCode());
-      destination_ = fun.CurrentCode();
-      ASSERT(!destination_.IsStubCode());
-    } else {
-      target_ = call.Get<Code::kSCallTableCodeTarget>();
-      ASSERT(target_.IsCode());
-      destination_ = Code::Cast(target_).raw();
-    }
+    // The precompiler should have already replaced all function entries
+    // with code entries.
+    ASSERT(call.Get<Code::kSCallTableFunctionTarget>() == Function::null());
+    target_ = call.Get<Code::kSCallTableCodeTarget>();
+    ASSERT(target_.IsCode());
+    destination_ = Code::Cast(target_).raw();
 
     // A call site can decide to jump not to the beginning of a function but
     // rather jump into it at a certain offset.
diff --git a/runtime/vm/compiler/runtime_api.cc b/runtime/vm/compiler/runtime_api.cc
index 7f92b3b..4f6221e 100644
--- a/runtime/vm/compiler/runtime_api.cc
+++ b/runtime/vm/compiler/runtime_api.cc
@@ -4,6 +4,20 @@
 
 #include "vm/compiler/runtime_api.h"
 
+#include "vm/object.h"
+
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/runtime_offsets_list.h"
+#include "vm/dart_entry.h"
+#include "vm/longjump.h"
+#include "vm/native_arguments.h"
+#include "vm/native_entry.h"
+#include "vm/object_store.h"
+#include "vm/runtime_entry.h"
+#include "vm/symbols.h"
+#include "vm/timeline.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 namespace compiler {
 namespace target {
@@ -14,23 +28,23 @@
   return Utils::IsInt(kSmiBits + 1, v);
 }
 
+bool WillAllocateNewOrRememberedContext(intptr_t num_context_variables) {
+  if (!dart::Context::IsValidLength(num_context_variables)) return false;
+  return dart::Heap::IsAllocatableInNewSpace(
+      dart::Context::InstanceSize(num_context_variables));
+}
+
+bool WillAllocateNewOrRememberedArray(intptr_t length) {
+  if (!dart::Array::IsValidLength(length)) return false;
+  return !dart::Array::UseCardMarkingForAllocation(length);
+}
+
 }  // namespace target
 }  // namespace compiler
 }  // namespace dart
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
 
-#include "vm/compiler/runtime_offsets_list.h"
-#include "vm/dart_entry.h"
-#include "vm/longjump.h"
-#include "vm/native_arguments.h"
-#include "vm/native_entry.h"
-#include "vm/object.h"
-#include "vm/object_store.h"
-#include "vm/runtime_entry.h"
-#include "vm/symbols.h"
-#include "vm/timeline.h"
-
 namespace dart {
 namespace compiler {
 
diff --git a/runtime/vm/compiler/runtime_api.h b/runtime/vm/compiler/runtime_api.h
index 5966425..c7f3303 100644
--- a/runtime/vm/compiler/runtime_api.h
+++ b/runtime/vm/compiler/runtime_api.h
@@ -362,6 +362,10 @@
 word ToRawPointer(const dart::Object& a);
 #endif  // defined(TARGET_ARCH_IA32)
 
+bool WillAllocateNewOrRememberedContext(intptr_t num_context_variables);
+
+bool WillAllocateNewOrRememberedArray(intptr_t length);
+
 //
 // Target specific offsets and constants.
 //
@@ -1071,11 +1075,12 @@
 
 class Isolate : public AllStatic {
  public:
-  static word object_store_offset();
+  static word cached_object_store_offset();
   static word default_tag_offset();
   static word current_tag_offset();
   static word user_tag_offset();
-  static word class_table_offset();
+  static word cached_class_table_table_offset();
+  static word shared_class_table_offset();
   static word ic_miss_code_offset();
 #if !defined(PRODUCT)
   static word single_step_offset();
@@ -1089,8 +1094,6 @@
 
 class ClassTable : public AllStatic {
  public:
-  static word table_offset();
-  static word shared_class_table_offset();
 #if !defined(PRODUCT)
   static word ClassOffsetFor(intptr_t cid);
   static word SharedTableOffsetFor();
diff --git a/runtime/vm/compiler/runtime_offsets_extracted.h b/runtime/vm/compiler/runtime_offsets_extracted.h
index 295a04b..b956285 100644
--- a/runtime/vm/compiler/runtime_offsets_extracted.h
+++ b/runtime/vm/compiler/runtime_offsets_extracted.h
@@ -81,9 +81,6 @@
 static constexpr dart::compiler::target::word
     Class_host_type_arguments_field_offset_in_words_offset = 104;
 static constexpr dart::compiler::target::word
-    ClassTable_shared_class_table_offset = 16;
-static constexpr dart::compiler::target::word ClassTable_table_offset = 8;
-static constexpr dart::compiler::target::word
     SharedClassTable_class_heap_stats_table_offset = 0;
 static constexpr dart::compiler::target::word Closure_context_offset = 20;
 static constexpr dart::compiler::target::word
@@ -137,12 +134,16 @@
 static constexpr dart::compiler::target::word ICData_state_bits_offset = 28;
 static constexpr dart::compiler::target::word
     ICData_receivers_static_type_offset = 16;
-static constexpr dart::compiler::target::word Isolate_class_table_offset = 36;
+static constexpr dart::compiler::target::word
+    Isolate_shared_class_table_offset = 36;
+static constexpr dart::compiler::target::word
+    Isolate_cached_class_table_table_offset = 40;
 static constexpr dart::compiler::target::word Isolate_current_tag_offset = 20;
 static constexpr dart::compiler::target::word Isolate_default_tag_offset = 24;
 static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 28;
-static constexpr dart::compiler::target::word Isolate_object_store_offset = 32;
-static constexpr dart::compiler::target::word Isolate_single_step_offset = 60;
+static constexpr dart::compiler::target::word
+    Isolate_cached_object_store_offset = 32;
+static constexpr dart::compiler::target::word Isolate_single_step_offset = 48;
 static constexpr dart::compiler::target::word Isolate_user_tag_offset = 16;
 static constexpr dart::compiler::target::word LinkedHashMap_data_offset = 16;
 static constexpr dart::compiler::target::word
@@ -555,9 +556,6 @@
 static constexpr dart::compiler::target::word
     Class_host_type_arguments_field_offset_in_words_offset = 184;
 static constexpr dart::compiler::target::word
-    ClassTable_shared_class_table_offset = 32;
-static constexpr dart::compiler::target::word ClassTable_table_offset = 16;
-static constexpr dart::compiler::target::word
     SharedClassTable_class_heap_stats_table_offset = 0;
 static constexpr dart::compiler::target::word Closure_context_offset = 40;
 static constexpr dart::compiler::target::word
@@ -611,12 +609,16 @@
 static constexpr dart::compiler::target::word ICData_state_bits_offset = 52;
 static constexpr dart::compiler::target::word
     ICData_receivers_static_type_offset = 32;
-static constexpr dart::compiler::target::word Isolate_class_table_offset = 72;
+static constexpr dart::compiler::target::word
+    Isolate_shared_class_table_offset = 72;
+static constexpr dart::compiler::target::word
+    Isolate_cached_class_table_table_offset = 80;
 static constexpr dart::compiler::target::word Isolate_current_tag_offset = 40;
 static constexpr dart::compiler::target::word Isolate_default_tag_offset = 48;
 static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 56;
-static constexpr dart::compiler::target::word Isolate_object_store_offset = 64;
-static constexpr dart::compiler::target::word Isolate_single_step_offset = 120;
+static constexpr dart::compiler::target::word
+    Isolate_cached_object_store_offset = 64;
+static constexpr dart::compiler::target::word Isolate_single_step_offset = 96;
 static constexpr dart::compiler::target::word Isolate_user_tag_offset = 32;
 static constexpr dart::compiler::target::word LinkedHashMap_data_offset = 32;
 static constexpr dart::compiler::target::word
@@ -1032,9 +1034,6 @@
 static constexpr dart::compiler::target::word
     Class_host_type_arguments_field_offset_in_words_offset = 104;
 static constexpr dart::compiler::target::word
-    ClassTable_shared_class_table_offset = 16;
-static constexpr dart::compiler::target::word ClassTable_table_offset = 8;
-static constexpr dart::compiler::target::word
     SharedClassTable_class_heap_stats_table_offset = 0;
 static constexpr dart::compiler::target::word Closure_context_offset = 20;
 static constexpr dart::compiler::target::word
@@ -1088,12 +1087,16 @@
 static constexpr dart::compiler::target::word ICData_state_bits_offset = 28;
 static constexpr dart::compiler::target::word
     ICData_receivers_static_type_offset = 16;
-static constexpr dart::compiler::target::word Isolate_class_table_offset = 36;
+static constexpr dart::compiler::target::word
+    Isolate_shared_class_table_offset = 36;
+static constexpr dart::compiler::target::word
+    Isolate_cached_class_table_table_offset = 40;
 static constexpr dart::compiler::target::word Isolate_current_tag_offset = 20;
 static constexpr dart::compiler::target::word Isolate_default_tag_offset = 24;
 static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 28;
-static constexpr dart::compiler::target::word Isolate_object_store_offset = 32;
-static constexpr dart::compiler::target::word Isolate_single_step_offset = 60;
+static constexpr dart::compiler::target::word
+    Isolate_cached_object_store_offset = 32;
+static constexpr dart::compiler::target::word Isolate_single_step_offset = 48;
 static constexpr dart::compiler::target::word Isolate_user_tag_offset = 16;
 static constexpr dart::compiler::target::word LinkedHashMap_data_offset = 16;
 static constexpr dart::compiler::target::word
@@ -1503,9 +1506,6 @@
 static constexpr dart::compiler::target::word
     Class_host_type_arguments_field_offset_in_words_offset = 184;
 static constexpr dart::compiler::target::word
-    ClassTable_shared_class_table_offset = 32;
-static constexpr dart::compiler::target::word ClassTable_table_offset = 16;
-static constexpr dart::compiler::target::word
     SharedClassTable_class_heap_stats_table_offset = 0;
 static constexpr dart::compiler::target::word Closure_context_offset = 40;
 static constexpr dart::compiler::target::word
@@ -1559,12 +1559,16 @@
 static constexpr dart::compiler::target::word ICData_state_bits_offset = 52;
 static constexpr dart::compiler::target::word
     ICData_receivers_static_type_offset = 32;
-static constexpr dart::compiler::target::word Isolate_class_table_offset = 72;
+static constexpr dart::compiler::target::word
+    Isolate_shared_class_table_offset = 72;
+static constexpr dart::compiler::target::word
+    Isolate_cached_class_table_table_offset = 80;
 static constexpr dart::compiler::target::word Isolate_current_tag_offset = 40;
 static constexpr dart::compiler::target::word Isolate_default_tag_offset = 48;
 static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 56;
-static constexpr dart::compiler::target::word Isolate_object_store_offset = 64;
-static constexpr dart::compiler::target::word Isolate_single_step_offset = 120;
+static constexpr dart::compiler::target::word
+    Isolate_cached_object_store_offset = 64;
+static constexpr dart::compiler::target::word Isolate_single_step_offset = 96;
 static constexpr dart::compiler::target::word Isolate_user_tag_offset = 32;
 static constexpr dart::compiler::target::word LinkedHashMap_data_offset = 32;
 static constexpr dart::compiler::target::word
@@ -1982,9 +1986,6 @@
 static constexpr dart::compiler::target::word Class_super_type_offset = 44;
 static constexpr dart::compiler::target::word
     Class_host_type_arguments_field_offset_in_words_offset = 104;
-static constexpr dart::compiler::target::word
-    ClassTable_shared_class_table_offset = 16;
-static constexpr dart::compiler::target::word ClassTable_table_offset = 8;
 static constexpr dart::compiler::target::word Closure_context_offset = 20;
 static constexpr dart::compiler::target::word
     Closure_delayed_type_arguments_offset = 12;
@@ -2037,11 +2038,15 @@
 static constexpr dart::compiler::target::word ICData_state_bits_offset = 28;
 static constexpr dart::compiler::target::word
     ICData_receivers_static_type_offset = 16;
-static constexpr dart::compiler::target::word Isolate_class_table_offset = 36;
+static constexpr dart::compiler::target::word
+    Isolate_shared_class_table_offset = 36;
+static constexpr dart::compiler::target::word
+    Isolate_cached_class_table_table_offset = 40;
 static constexpr dart::compiler::target::word Isolate_current_tag_offset = 20;
 static constexpr dart::compiler::target::word Isolate_default_tag_offset = 24;
 static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 28;
-static constexpr dart::compiler::target::word Isolate_object_store_offset = 32;
+static constexpr dart::compiler::target::word
+    Isolate_cached_object_store_offset = 32;
 static constexpr dart::compiler::target::word Isolate_user_tag_offset = 16;
 static constexpr dart::compiler::target::word LinkedHashMap_data_offset = 16;
 static constexpr dart::compiler::target::word
@@ -2450,9 +2455,6 @@
 static constexpr dart::compiler::target::word Class_super_type_offset = 88;
 static constexpr dart::compiler::target::word
     Class_host_type_arguments_field_offset_in_words_offset = 184;
-static constexpr dart::compiler::target::word
-    ClassTable_shared_class_table_offset = 32;
-static constexpr dart::compiler::target::word ClassTable_table_offset = 16;
 static constexpr dart::compiler::target::word Closure_context_offset = 40;
 static constexpr dart::compiler::target::word
     Closure_delayed_type_arguments_offset = 24;
@@ -2505,11 +2507,15 @@
 static constexpr dart::compiler::target::word ICData_state_bits_offset = 52;
 static constexpr dart::compiler::target::word
     ICData_receivers_static_type_offset = 32;
-static constexpr dart::compiler::target::word Isolate_class_table_offset = 72;
+static constexpr dart::compiler::target::word
+    Isolate_shared_class_table_offset = 72;
+static constexpr dart::compiler::target::word
+    Isolate_cached_class_table_table_offset = 80;
 static constexpr dart::compiler::target::word Isolate_current_tag_offset = 40;
 static constexpr dart::compiler::target::word Isolate_default_tag_offset = 48;
 static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 56;
-static constexpr dart::compiler::target::word Isolate_object_store_offset = 64;
+static constexpr dart::compiler::target::word
+    Isolate_cached_object_store_offset = 64;
 static constexpr dart::compiler::target::word Isolate_user_tag_offset = 32;
 static constexpr dart::compiler::target::word LinkedHashMap_data_offset = 32;
 static constexpr dart::compiler::target::word
@@ -2921,9 +2927,6 @@
 static constexpr dart::compiler::target::word Class_super_type_offset = 44;
 static constexpr dart::compiler::target::word
     Class_host_type_arguments_field_offset_in_words_offset = 104;
-static constexpr dart::compiler::target::word
-    ClassTable_shared_class_table_offset = 16;
-static constexpr dart::compiler::target::word ClassTable_table_offset = 8;
 static constexpr dart::compiler::target::word Closure_context_offset = 20;
 static constexpr dart::compiler::target::word
     Closure_delayed_type_arguments_offset = 12;
@@ -2976,11 +2979,15 @@
 static constexpr dart::compiler::target::word ICData_state_bits_offset = 28;
 static constexpr dart::compiler::target::word
     ICData_receivers_static_type_offset = 16;
-static constexpr dart::compiler::target::word Isolate_class_table_offset = 36;
+static constexpr dart::compiler::target::word
+    Isolate_shared_class_table_offset = 36;
+static constexpr dart::compiler::target::word
+    Isolate_cached_class_table_table_offset = 40;
 static constexpr dart::compiler::target::word Isolate_current_tag_offset = 20;
 static constexpr dart::compiler::target::word Isolate_default_tag_offset = 24;
 static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 28;
-static constexpr dart::compiler::target::word Isolate_object_store_offset = 32;
+static constexpr dart::compiler::target::word
+    Isolate_cached_object_store_offset = 32;
 static constexpr dart::compiler::target::word Isolate_user_tag_offset = 16;
 static constexpr dart::compiler::target::word LinkedHashMap_data_offset = 16;
 static constexpr dart::compiler::target::word
@@ -3386,9 +3393,6 @@
 static constexpr dart::compiler::target::word Class_super_type_offset = 88;
 static constexpr dart::compiler::target::word
     Class_host_type_arguments_field_offset_in_words_offset = 184;
-static constexpr dart::compiler::target::word
-    ClassTable_shared_class_table_offset = 32;
-static constexpr dart::compiler::target::word ClassTable_table_offset = 16;
 static constexpr dart::compiler::target::word Closure_context_offset = 40;
 static constexpr dart::compiler::target::word
     Closure_delayed_type_arguments_offset = 24;
@@ -3441,11 +3445,15 @@
 static constexpr dart::compiler::target::word ICData_state_bits_offset = 52;
 static constexpr dart::compiler::target::word
     ICData_receivers_static_type_offset = 32;
-static constexpr dart::compiler::target::word Isolate_class_table_offset = 72;
+static constexpr dart::compiler::target::word
+    Isolate_shared_class_table_offset = 72;
+static constexpr dart::compiler::target::word
+    Isolate_cached_class_table_table_offset = 80;
 static constexpr dart::compiler::target::word Isolate_current_tag_offset = 40;
 static constexpr dart::compiler::target::word Isolate_default_tag_offset = 48;
 static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 56;
-static constexpr dart::compiler::target::word Isolate_object_store_offset = 64;
+static constexpr dart::compiler::target::word
+    Isolate_cached_object_store_offset = 64;
 static constexpr dart::compiler::target::word Isolate_user_tag_offset = 32;
 static constexpr dart::compiler::target::word LinkedHashMap_data_offset = 32;
 static constexpr dart::compiler::target::word
@@ -3868,9 +3876,6 @@
 static constexpr dart::compiler::target::word
     AOT_Class_host_type_arguments_field_offset_in_words_offset = 104;
 static constexpr dart::compiler::target::word
-    AOT_ClassTable_shared_class_table_offset = 16;
-static constexpr dart::compiler::target::word AOT_ClassTable_table_offset = 8;
-static constexpr dart::compiler::target::word
     AOT_SharedClassTable_class_heap_stats_table_offset = 0;
 static constexpr dart::compiler::target::word AOT_Closure_context_offset = 20;
 static constexpr dart::compiler::target::word
@@ -3913,18 +3918,20 @@
 static constexpr dart::compiler::target::word AOT_ICData_NumArgsTestedMask = 3;
 static constexpr dart::compiler::target::word AOT_ICData_NumArgsTestedShift = 0;
 static constexpr dart::compiler::target::word AOT_ICData_entries_offset = 12;
-static constexpr dart::compiler::target::word AOT_Isolate_class_table_offset =
-    36;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_shared_class_table_offset = 36;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_cached_class_table_table_offset = 40;
 static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
     20;
 static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     24;
 static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
     28;
-static constexpr dart::compiler::target::word AOT_Isolate_object_store_offset =
-    32;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_cached_object_store_offset = 32;
 static constexpr dart::compiler::target::word AOT_Isolate_single_step_offset =
-    60;
+    48;
 static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 16;
 static constexpr dart::compiler::target::word AOT_LinkedHashMap_data_offset =
     16;
@@ -4386,9 +4393,6 @@
 static constexpr dart::compiler::target::word
     AOT_Class_host_type_arguments_field_offset_in_words_offset = 184;
 static constexpr dart::compiler::target::word
-    AOT_ClassTable_shared_class_table_offset = 32;
-static constexpr dart::compiler::target::word AOT_ClassTable_table_offset = 16;
-static constexpr dart::compiler::target::word
     AOT_SharedClassTable_class_heap_stats_table_offset = 0;
 static constexpr dart::compiler::target::word AOT_Closure_context_offset = 40;
 static constexpr dart::compiler::target::word
@@ -4431,18 +4435,20 @@
 static constexpr dart::compiler::target::word AOT_ICData_NumArgsTestedMask = 3;
 static constexpr dart::compiler::target::word AOT_ICData_NumArgsTestedShift = 0;
 static constexpr dart::compiler::target::word AOT_ICData_entries_offset = 24;
-static constexpr dart::compiler::target::word AOT_Isolate_class_table_offset =
-    72;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_shared_class_table_offset = 72;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_cached_class_table_table_offset = 80;
 static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
     40;
 static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     48;
 static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
     56;
-static constexpr dart::compiler::target::word AOT_Isolate_object_store_offset =
-    64;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_cached_object_store_offset = 64;
 static constexpr dart::compiler::target::word AOT_Isolate_single_step_offset =
-    120;
+    96;
 static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 32;
 static constexpr dart::compiler::target::word AOT_LinkedHashMap_data_offset =
     32;
@@ -4910,9 +4916,6 @@
 static constexpr dart::compiler::target::word
     AOT_Class_host_type_arguments_field_offset_in_words_offset = 184;
 static constexpr dart::compiler::target::word
-    AOT_ClassTable_shared_class_table_offset = 32;
-static constexpr dart::compiler::target::word AOT_ClassTable_table_offset = 16;
-static constexpr dart::compiler::target::word
     AOT_SharedClassTable_class_heap_stats_table_offset = 0;
 static constexpr dart::compiler::target::word AOT_Closure_context_offset = 40;
 static constexpr dart::compiler::target::word
@@ -4955,18 +4958,20 @@
 static constexpr dart::compiler::target::word AOT_ICData_NumArgsTestedMask = 3;
 static constexpr dart::compiler::target::word AOT_ICData_NumArgsTestedShift = 0;
 static constexpr dart::compiler::target::word AOT_ICData_entries_offset = 24;
-static constexpr dart::compiler::target::word AOT_Isolate_class_table_offset =
-    72;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_shared_class_table_offset = 72;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_cached_class_table_table_offset = 80;
 static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
     40;
 static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     48;
 static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
     56;
-static constexpr dart::compiler::target::word AOT_Isolate_object_store_offset =
-    64;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_cached_object_store_offset = 64;
 static constexpr dart::compiler::target::word AOT_Isolate_single_step_offset =
-    120;
+    96;
 static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 32;
 static constexpr dart::compiler::target::word AOT_LinkedHashMap_data_offset =
     32;
@@ -5433,9 +5438,6 @@
 static constexpr dart::compiler::target::word AOT_Class_super_type_offset = 44;
 static constexpr dart::compiler::target::word
     AOT_Class_host_type_arguments_field_offset_in_words_offset = 104;
-static constexpr dart::compiler::target::word
-    AOT_ClassTable_shared_class_table_offset = 16;
-static constexpr dart::compiler::target::word AOT_ClassTable_table_offset = 8;
 static constexpr dart::compiler::target::word AOT_Closure_context_offset = 20;
 static constexpr dart::compiler::target::word
     AOT_Closure_delayed_type_arguments_offset = 12;
@@ -5477,16 +5479,18 @@
 static constexpr dart::compiler::target::word AOT_ICData_NumArgsTestedMask = 3;
 static constexpr dart::compiler::target::word AOT_ICData_NumArgsTestedShift = 0;
 static constexpr dart::compiler::target::word AOT_ICData_entries_offset = 12;
-static constexpr dart::compiler::target::word AOT_Isolate_class_table_offset =
-    36;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_shared_class_table_offset = 36;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_cached_class_table_table_offset = 40;
 static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
     20;
 static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     24;
 static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
     28;
-static constexpr dart::compiler::target::word AOT_Isolate_object_store_offset =
-    32;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_cached_object_store_offset = 32;
 static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 16;
 static constexpr dart::compiler::target::word AOT_LinkedHashMap_data_offset =
     16;
@@ -5944,9 +5948,6 @@
 static constexpr dart::compiler::target::word AOT_Class_super_type_offset = 88;
 static constexpr dart::compiler::target::word
     AOT_Class_host_type_arguments_field_offset_in_words_offset = 184;
-static constexpr dart::compiler::target::word
-    AOT_ClassTable_shared_class_table_offset = 32;
-static constexpr dart::compiler::target::word AOT_ClassTable_table_offset = 16;
 static constexpr dart::compiler::target::word AOT_Closure_context_offset = 40;
 static constexpr dart::compiler::target::word
     AOT_Closure_delayed_type_arguments_offset = 24;
@@ -5988,16 +5989,18 @@
 static constexpr dart::compiler::target::word AOT_ICData_NumArgsTestedMask = 3;
 static constexpr dart::compiler::target::word AOT_ICData_NumArgsTestedShift = 0;
 static constexpr dart::compiler::target::word AOT_ICData_entries_offset = 24;
-static constexpr dart::compiler::target::word AOT_Isolate_class_table_offset =
-    72;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_shared_class_table_offset = 72;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_cached_class_table_table_offset = 80;
 static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
     40;
 static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     48;
 static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
     56;
-static constexpr dart::compiler::target::word AOT_Isolate_object_store_offset =
-    64;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_cached_object_store_offset = 64;
 static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 32;
 static constexpr dart::compiler::target::word AOT_LinkedHashMap_data_offset =
     32;
@@ -6461,9 +6464,6 @@
 static constexpr dart::compiler::target::word AOT_Class_super_type_offset = 88;
 static constexpr dart::compiler::target::word
     AOT_Class_host_type_arguments_field_offset_in_words_offset = 184;
-static constexpr dart::compiler::target::word
-    AOT_ClassTable_shared_class_table_offset = 32;
-static constexpr dart::compiler::target::word AOT_ClassTable_table_offset = 16;
 static constexpr dart::compiler::target::word AOT_Closure_context_offset = 40;
 static constexpr dart::compiler::target::word
     AOT_Closure_delayed_type_arguments_offset = 24;
@@ -6505,16 +6505,18 @@
 static constexpr dart::compiler::target::word AOT_ICData_NumArgsTestedMask = 3;
 static constexpr dart::compiler::target::word AOT_ICData_NumArgsTestedShift = 0;
 static constexpr dart::compiler::target::word AOT_ICData_entries_offset = 24;
-static constexpr dart::compiler::target::word AOT_Isolate_class_table_offset =
-    72;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_shared_class_table_offset = 72;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_cached_class_table_table_offset = 80;
 static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
     40;
 static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     48;
 static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
     56;
-static constexpr dart::compiler::target::word AOT_Isolate_object_store_offset =
-    64;
+static constexpr dart::compiler::target::word
+    AOT_Isolate_cached_object_store_offset = 64;
 static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 32;
 static constexpr dart::compiler::target::word AOT_LinkedHashMap_data_offset =
     32;
diff --git a/runtime/vm/compiler/runtime_offsets_list.h b/runtime/vm/compiler/runtime_offsets_list.h
index 229ff56..6294726 100644
--- a/runtime/vm/compiler/runtime_offsets_list.h
+++ b/runtime/vm/compiler/runtime_offsets_list.h
@@ -66,8 +66,6 @@
   FIELD(Class, num_type_arguments_offset)                                      \
   FIELD(Class, super_type_offset)                                              \
   FIELD(Class, host_type_arguments_field_offset_in_words_offset)               \
-  FIELD(ClassTable, shared_class_table_offset)                                 \
-  FIELD(ClassTable, table_offset)                                              \
   NOT_IN_PRODUCT(FIELD(SharedClassTable, class_heap_stats_table_offset))       \
   FIELD(Closure, context_offset)                                               \
   FIELD(Closure, delayed_type_arguments_offset)                                \
@@ -106,11 +104,12 @@
   PRECOMP_NO_CHECK(FIELD(ICData, owner_offset))                                \
   PRECOMP_NO_CHECK(FIELD(ICData, state_bits_offset))                           \
   NOT_IN_PRECOMPILED_RUNTIME(FIELD(ICData, receivers_static_type_offset))      \
-  FIELD(Isolate, class_table_offset)                                           \
+  FIELD(Isolate, shared_class_table_offset)                                    \
+  FIELD(Isolate, cached_class_table_table_offset)                              \
   FIELD(Isolate, current_tag_offset)                                           \
   FIELD(Isolate, default_tag_offset)                                           \
   FIELD(Isolate, ic_miss_code_offset)                                          \
-  FIELD(Isolate, object_store_offset)                                          \
+  FIELD(Isolate, cached_object_store_offset)                                   \
   NOT_IN_PRODUCT(FIELD(Isolate, single_step_offset))                           \
   FIELD(Isolate, user_tag_offset)                                              \
   FIELD(LinkedHashMap, data_offset)                                            \
diff --git a/runtime/vm/compiler/stub_code_compiler.h b/runtime/vm/compiler/stub_code_compiler.h
index e4a9429..35f23fd 100644
--- a/runtime/vm/compiler/stub_code_compiler.h
+++ b/runtime/vm/compiler/stub_code_compiler.h
@@ -50,7 +50,6 @@
       const Object& context_allocation_stub);
 #endif
 
-#if !defined(DART_PRECOMPILED_RUNTIME)
   static RawArray* BuildStaticCallsTable(
       Zone* zone,
       compiler::UnresolvedPcRelativeCalls* unresolved_calls);
@@ -121,31 +120,12 @@
 
   static void GenerateJITCallbackTrampolines(Assembler* assembler,
                                              intptr_t next_callback_id);
-
-#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 };
 
 }  // namespace compiler
 
 enum DeoptStubKind { kLazyDeoptFromReturn, kLazyDeoptFromThrow, kEagerDeopt };
 
-// Invocation mode for TypeCheck runtime entry that describes
-// where we are calling it from.
-enum TypeCheckMode {
-  // TypeCheck is invoked from LazySpecializeTypeTest stub.
-  // It should replace stub on the type with a specialized version.
-  kTypeCheckFromLazySpecializeStub,
-
-  // TypeCheck is invoked from the SlowTypeTest stub.
-  // This means that cache can be lazily created (if needed)
-  // and dst_name can be fetched from the pool.
-  kTypeCheckFromSlowStub,
-
-  // TypeCheck is invoked from normal inline AssertAssignable.
-  // Both cache and dst_name must be already populated.
-  kTypeCheckFromInline
-};
-
 // Zap value used to indicate unused CODE_REG in deopt.
 static const uword kZapCodeReg = 0xf1f1f1f1;
 
diff --git a/runtime/vm/compiler/stub_code_compiler_arm.cc b/runtime/vm/compiler/stub_code_compiler_arm.cc
index 1e817e7..8a6858b 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm.cc
@@ -16,6 +16,7 @@
 
 #include "vm/class_id.h"
 #include "vm/code_entry_kind.h"
+#include "vm/compiler/api/type_check_mode.h"
 #include "vm/compiler/assembler/assembler.h"
 #include "vm/compiler/backend/locations.h"
 #include "vm/constants.h"
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index 0730c37..ced8835 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -15,6 +15,7 @@
 
 #include "vm/class_id.h"
 #include "vm/code_entry_kind.h"
+#include "vm/compiler/api/type_check_mode.h"
 #include "vm/compiler/assembler/assembler.h"
 #include "vm/compiler/backend/locations.h"
 #include "vm/constants.h"
diff --git a/runtime/vm/compiler/stub_code_compiler_ia32.cc b/runtime/vm/compiler/stub_code_compiler_ia32.cc
index 53b0cc1..15a5c89 100644
--- a/runtime/vm/compiler/stub_code_compiler_ia32.cc
+++ b/runtime/vm/compiler/stub_code_compiler_ia32.cc
@@ -15,6 +15,7 @@
 
 #include "vm/class_id.h"
 #include "vm/code_entry_kind.h"
+#include "vm/compiler/api/type_check_mode.h"
 #include "vm/compiler/assembler/assembler.h"
 #include "vm/compiler/backend/locations.h"
 #include "vm/constants.h"
diff --git a/runtime/vm/compiler/stub_code_compiler_x64.cc b/runtime/vm/compiler/stub_code_compiler_x64.cc
index d981a03..9a2b2c7 100644
--- a/runtime/vm/compiler/stub_code_compiler_x64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_x64.cc
@@ -17,6 +17,7 @@
 
 #include "vm/class_id.h"
 #include "vm/code_entry_kind.h"
+#include "vm/compiler/api/type_check_mode.h"
 #include "vm/compiler/assembler/assembler.h"
 #include "vm/constants.h"
 #include "vm/instructions.h"
diff --git a/runtime/vm/constants_arm.h b/runtime/vm/constants_arm.h
index 48e015e..b068e7f 100644
--- a/runtime/vm/constants_arm.h
+++ b/runtime/vm/constants_arm.h
@@ -947,6 +947,8 @@
 float ReciprocalSqrtEstimate(float op);
 float ReciprocalSqrtStep(float op1, float op2);
 
+constexpr uword kBreakInstructionFiller = 0xE1200070;  // bkpt #0
+
 }  // namespace dart
 
 #endif  // RUNTIME_VM_CONSTANTS_ARM_H_
diff --git a/runtime/vm/constants_arm64.h b/runtime/vm/constants_arm64.h
index a7852d6..9929f0d 100644
--- a/runtime/vm/constants_arm64.h
+++ b/runtime/vm/constants_arm64.h
@@ -1326,6 +1326,8 @@
   DISALLOW_IMPLICIT_CONSTRUCTORS(Instr);
 };
 
+const uword kBreakInstructionFiller = 0xD4200000D4200000L;  // brk #0; brk #0
+
 }  // namespace dart
 
 #endif  // RUNTIME_VM_CONSTANTS_ARM64_H_
diff --git a/runtime/vm/constants_ia32.h b/runtime/vm/constants_ia32.h
index 398fdcc..2f7d689 100644
--- a/runtime/vm/constants_ia32.h
+++ b/runtime/vm/constants_ia32.h
@@ -247,6 +247,8 @@
   static constexpr ExtensionStrategy kArgumentStackExtension = kExtendedTo4;
 };
 
+const uword kBreakInstructionFiller = 0xCCCCCCCC;
+
 }  // namespace dart
 
 #endif  // RUNTIME_VM_CONSTANTS_IA32_H_
diff --git a/runtime/vm/constants_x64.h b/runtime/vm/constants_x64.h
index fe2e01d..f4030c9 100644
--- a/runtime/vm/constants_x64.h
+++ b/runtime/vm/constants_x64.h
@@ -408,6 +408,8 @@
 // becomes important to us.
 const int MAX_NOP_SIZE = 8;
 
+const uword kBreakInstructionFiller = 0xCCCCCCCCCCCCCCCCL;
+
 }  // namespace dart
 
 #endif  // RUNTIME_VM_CONSTANTS_X64_H_
diff --git a/runtime/vm/cpu_x64.cc b/runtime/vm/cpu_x64.cc
index c892d85..7f5981c 100644
--- a/runtime/vm/cpu_x64.cc
+++ b/runtime/vm/cpu_x64.cc
@@ -8,7 +8,6 @@
 #include "vm/cpu.h"
 #include "vm/cpu_x64.h"
 
-#include "vm/compiler/assembler/assembler.h"
 #include "vm/constants.h"
 #include "vm/cpuinfo.h"
 #include "vm/heap/heap.h"
diff --git a/runtime/vm/dart.cc b/runtime/vm/dart.cc
index ad905b6..f295519 100644
--- a/runtime/vm/dart.cc
+++ b/runtime/vm/dart.cc
@@ -248,7 +248,9 @@
     std::unique_ptr<IsolateGroupSource> source(
         new IsolateGroupSource(nullptr, kVmIsolateName, vm_isolate_snapshot,
                                instructions_snapshot, nullptr, -1, api_flags));
-    auto group = new IsolateGroup(std::move(source), /*embedder_data=*/nullptr);
+    // ObjectStore should be created later, after null objects are initialized.
+    auto group = new IsolateGroup(std::move(source), /*embedder_data=*/nullptr,
+                                  /*object_store=*/nullptr);
     group->CreateHeap(/*is_vm_isolate=*/true,
                       /*is_service_or_kernel_isolate=*/false);
     IsolateGroup::RegisterIsolateGroup(group);
@@ -265,7 +267,8 @@
     StackZone zone(T);
     HandleScope handle_scope(T);
     Object::InitNullAndBool(vm_isolate_);
-    ObjectStore::Init(vm_isolate_);
+    vm_isolate_->set_object_store(new ObjectStore());
+    vm_isolate_->isolate_object_store()->Init();
     TargetCPUFeatures::Init();
     Object::Init(vm_isolate_);
     ArgumentsDescriptor::Init();
@@ -653,25 +656,35 @@
   return Snapshot::IsFull(isolate_kind);
 }
 
-RawError* Dart::InitializeIsolate(const uint8_t* snapshot_data,
-                                  const uint8_t* snapshot_instructions,
-                                  const uint8_t* kernel_buffer,
-                                  intptr_t kernel_buffer_size,
-                                  void* isolate_data) {
-  // Initialize the new isolate.
-  Thread* T = Thread::Current();
-  Isolate* I = T->isolate();
-#if defined(SUPPORT_TIMELINE)
-  TimelineBeginEndScope tbes(T, Timeline::GetIsolateStream(),
-                             "InitializeIsolate");
-  tbes.SetNumArguments(1);
-  tbes.CopyArgument(0, "isolateName", I->name());
-#endif
-  ASSERT(I != NULL);
-  StackZone zone(T);
-  HandleScope handle_scope(T);
-  ObjectStore::Init(I);
+#if defined(DART_PRECOMPILED_RUNTIME)
+static bool CloneIntoChildIsolateAOT(Thread* T,
+                                     Isolate* I,
+                                     IsolateGroup* source_isolate_group) {
+  // In AOT we speed up isolate spawning by copying donor's isolate structure.
+  Isolate* donor_isolate = source_isolate_group != nullptr
+                               ? source_isolate_group->FirstIsolate()
+                               : nullptr;
+  if (donor_isolate == nullptr) {
+    return false;
+  }
+  I->isolate_object_store()->Init();
+  I->isolate_object_store()->PreallocateObjects();
+  // Initialize field_table with initial values.
+  I->set_field_table(T, donor_isolate->saved_initial_field_table()->Clone());
+  I->set_saved_initial_field_table(
+      donor_isolate->saved_initial_field_table_shareable());
 
+  ReversePcLookupCache::BuildAndAttachToIsolate(I);
+  return true;
+}
+#endif
+
+RawError* Dart::InitIsolateFromSnapshot(Thread* T,
+                                        Isolate* I,
+                                        const uint8_t* snapshot_data,
+                                        const uint8_t* snapshot_instructions,
+                                        const uint8_t* kernel_buffer,
+                                        intptr_t kernel_buffer_size) {
   Error& error = Error::Handle(T->zone());
   error = Object::Init(I, kernel_buffer, kernel_buffer_size);
   if (!error.IsNull()) {
@@ -681,8 +694,8 @@
     // Read the snapshot and setup the initial state.
 #if defined(SUPPORT_TIMELINE)
     TimelineBeginEndScope tbes(T, Timeline::GetIsolateStream(),
-                               "ReadIsolateSnapshot");
-#endif
+                               "ReadProgramSnapshot");
+#endif  // defined(SUPPORT_TIMELINE)
     // TODO(turnidge): Remove once length is not part of the snapshot.
     const Snapshot* snapshot = Snapshot::SetupFromBuffer(snapshot_data);
     if (snapshot == NULL) {
@@ -700,7 +713,7 @@
       OS::PrintErr("Size of isolate snapshot = %" Pd "\n", snapshot->length());
     }
     FullSnapshotReader reader(snapshot, snapshot_instructions, T);
-    const Error& error = Error::Handle(reader.ReadIsolateSnapshot());
+    const Error& error = Error::Handle(reader.ReadProgramSnapshot());
     if (!error.IsNull()) {
       return error.raw();
     }
@@ -714,7 +727,7 @@
       tbes.FormatArgument(1, "heapSize", "%" Pd64,
                           I->heap()->UsedInWords(Heap::kOld) * kWordSize);
     }
-#endif  // !defined(PRODUCT)
+#endif  // defined(SUPPORT_TIMELINE)
     if (FLAG_trace_isolates) {
       I->heap()->PrintSizes();
       MegamorphicCacheTable::PrintSizes(I);
@@ -727,6 +740,89 @@
     }
   }
 
+  return Error::null();
+}
+
+#if defined(DART_PRECOMPILED_RUNTIME)
+static void PrintLLVMConstantPool(Thread* T, Isolate* I) {
+  StackZone printing_zone(T);
+  HandleScope printing_scope(T);
+  TextBuffer b(1000);
+  const auto& constants =
+      GrowableObjectArray::Handle(I->object_store()->llvm_constant_pool());
+  if (constants.IsNull()) {
+    b.AddString("No constant pool information in snapshot.\n\n");
+  } else {
+    auto const len = constants.Length();
+    b.Printf("Constant pool contents (length %" Pd "):\n", len);
+    auto& obj = Object::Handle();
+    for (intptr_t i = 0; i < len; i++) {
+      obj = constants.At(i);
+      b.Printf("  %5" Pd ": ", i);
+      if (obj.IsString()) {
+        b.AddChar('"');
+        b.AddEscapedString(obj.ToCString());
+        b.AddChar('"');
+      } else {
+        b.AddString(obj.ToCString());
+      }
+      b.AddChar('\n');
+    }
+    b.AddString("End of constant pool.\n\n");
+  }
+  const auto& functions =
+      GrowableObjectArray::Handle(I->object_store()->llvm_function_pool());
+  if (functions.IsNull()) {
+    b.AddString("No function pool information in snapshot.\n\n");
+  } else {
+    auto const len = functions.Length();
+    b.Printf("Function pool contents (length %" Pd "):\n", len);
+    auto& obj = Function::Handle();
+    for (intptr_t i = 0; i < len; i++) {
+      obj ^= functions.At(i);
+      ASSERT(!obj.IsNull());
+      b.Printf("  %5" Pd ": %s\n", i, obj.ToFullyQualifiedCString());
+    }
+    b.AddString("End of function pool.\n\n");
+  }
+  THR_Print("%s", b.buf());
+}
+#endif
+
+RawError* Dart::InitializeIsolate(const uint8_t* snapshot_data,
+                                  const uint8_t* snapshot_instructions,
+                                  const uint8_t* kernel_buffer,
+                                  intptr_t kernel_buffer_size,
+                                  IsolateGroup* source_isolate_group,
+                                  void* isolate_data) {
+  // Initialize the new isolate.
+  Thread* T = Thread::Current();
+  Isolate* I = T->isolate();
+#if defined(SUPPORT_TIMELINE)
+  TimelineBeginEndScope tbes(T, Timeline::GetIsolateStream(),
+                             "InitializeIsolate");
+  tbes.SetNumArguments(1);
+  tbes.CopyArgument(0, "isolateName", I->name());
+#endif
+  ASSERT(I != NULL);
+  StackZone zone(T);
+  HandleScope handle_scope(T);
+  bool was_child_cloned_into_existing_isolate = false;
+#if defined(DART_PRECOMPILED_RUNTIME)
+  if (CloneIntoChildIsolateAOT(T, I, source_isolate_group)) {
+    was_child_cloned_into_existing_isolate = true;
+  } else {
+#endif
+    const Error& error = Error::Handle(
+        InitIsolateFromSnapshot(T, I, snapshot_data, snapshot_instructions,
+                                kernel_buffer, kernel_buffer_size));
+    if (!error.IsNull()) {
+      return error.raw();
+    }
+#if defined(DART_PRECOMPILED_RUNTIME)
+  }
+#endif
+
   Object::VerifyBuiltinVtables();
   DEBUG_ONLY(I->heap()->Verify(kForbidMarked));
 
@@ -735,47 +831,7 @@
   ASSERT(I->object_store()->megamorphic_call_miss_code() != Code::null());
   ASSERT(I->object_store()->build_method_extractor_code() != Code::null());
   if (FLAG_print_llvm_constant_pool) {
-    StackZone printing_zone(T);
-    HandleScope printing_scope(T);
-    TextBuffer b(1000);
-    const auto& constants =
-        GrowableObjectArray::Handle(I->object_store()->llvm_constant_pool());
-    if (constants.IsNull()) {
-      b.AddString("No constant pool information in snapshot.\n\n");
-    } else {
-      auto const len = constants.Length();
-      b.Printf("Constant pool contents (length %" Pd "):\n", len);
-      auto& obj = Object::Handle();
-      for (intptr_t i = 0; i < len; i++) {
-        obj = constants.At(i);
-        b.Printf("  %5" Pd ": ", i);
-        if (obj.IsString()) {
-          b.AddChar('"');
-          b.AddEscapedString(obj.ToCString());
-          b.AddChar('"');
-        } else {
-          b.AddString(obj.ToCString());
-        }
-        b.AddChar('\n');
-      }
-      b.AddString("End of constant pool.\n\n");
-    }
-    const auto& functions =
-        GrowableObjectArray::Handle(I->object_store()->llvm_function_pool());
-    if (functions.IsNull()) {
-      b.AddString("No function pool information in snapshot.\n\n");
-    } else {
-      auto const len = functions.Length();
-      b.Printf("Function pool contents (length %" Pd "):\n", len);
-      auto& obj = Function::Handle();
-      for (intptr_t i = 0; i < len; i++) {
-        obj ^= functions.At(i);
-        ASSERT(!obj.IsNull());
-        b.Printf("  %5" Pd ": %s\n", i, obj.ToFullyQualifiedCString());
-      }
-      b.AddString("End of function pool.\n\n");
-    }
-    THR_Print("%s", b.buf());
+    PrintLLVMConstantPool(T, I);
   }
 #else
   // JIT: The megamorphic call miss function and code come from the snapshot in
@@ -794,13 +850,20 @@
   I->set_ic_miss_code(StubCode::SwitchableCallMiss());
 
   if ((snapshot_data == NULL) || (kernel_buffer != NULL)) {
-    const Error& error = Error::Handle(I->object_store()->PreallocateObjects());
+    Error& error = Error::Handle();
+    error ^= I->object_store()->PreallocateObjects();
+    if (!error.IsNull()) {
+      return error.raw();
+    }
+    error ^= I->isolate_object_store()->PreallocateObjects();
     if (!error.IsNull()) {
       return error.raw();
     }
   }
 
-  I->heap()->InitGrowthControl();
+  if (!was_child_cloned_into_existing_isolate) {
+    I->heap()->InitGrowthControl();
+  }
   I->set_init_callback_data(isolate_data);
   if (FLAG_print_class_table) {
     I->class_table()->Print();
diff --git a/runtime/vm/dart.h b/runtime/vm/dart.h
index 0c7b6e8..537c0a7 100644
--- a/runtime/vm/dart.h
+++ b/runtime/vm/dart.h
@@ -59,7 +59,14 @@
                                      const uint8_t* snapshot_instructions,
                                      const uint8_t* kernel_buffer,
                                      intptr_t kernel_buffer_size,
+                                     IsolateGroup* source_isolate_group,
                                      void* data);
+  static RawError* InitIsolateFromSnapshot(Thread* T,
+                                           Isolate* I,
+                                           const uint8_t* snapshot_data,
+                                           const uint8_t* snapshot_instructions,
+                                           const uint8_t* kernel_buffer,
+                                           intptr_t kernel_buffer_size);
   static void RunShutdownCallback();
   static void ShutdownIsolate(Isolate* isolate);
   static void ShutdownIsolate();
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 3ff092c..6d3cf56 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -22,11 +22,7 @@
 #include "vm/debugger.h"
 #include "vm/dwarf.h"
 #include "vm/elf.h"
-#if !defined(DART_PRECOMPILED_RUNTIME)
-#include "vm/kernel_loader.h"
-#endif
 #include "platform/unicode.h"
-#include "vm/compiler/aot/precompiler.h"
 #include "vm/exceptions.h"
 #include "vm/flags.h"
 #include "vm/growable_array.h"
@@ -59,6 +55,11 @@
 #include "vm/uri.h"
 #include "vm/version.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/aot/precompiler.h"
+#include "vm/kernel_loader.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 // Facilitate quick access to the current zone once we have the current thread.
@@ -1141,10 +1142,10 @@
     // Api Handles when an error is encountered.
     T->EnterApiScope();
     const Error& error_obj = Error::Handle(
-        Z, Dart::InitializeIsolate(source->snapshot_data,
-                                   source->snapshot_instructions,
-                                   source->kernel_buffer,
-                                   source->kernel_buffer_size, isolate_data));
+        Z, Dart::InitializeIsolate(
+               source->snapshot_data, source->snapshot_instructions,
+               source->kernel_buffer, source->kernel_buffer_size,
+               is_new_group ? nullptr : group, isolate_data));
     if (error_obj.IsNull()) {
 #if defined(DEBUG) && !defined(DART_PRECOMPILED_RUNTIME)
       if (FLAG_check_function_fingerprints && source->kernel_buffer == NULL) {
@@ -1193,9 +1194,33 @@
   return false;
 }
 
+Isolate* CreateWithinExistingIsolateGroupAOT(IsolateGroup* group,
+                                             const char* name,
+                                             char** error) {
+#if defined(DART_PRECOMPILED_RUNTIME)
+  API_TIMELINE_DURATION(Thread::Current());
+  CHECK_NO_ISOLATE(Isolate::Current());
+
+  auto spawning_group = group;
+
+  Isolate* isolate = reinterpret_cast<Isolate*>(
+      CreateIsolate(spawning_group, /*is_new_group=*/false, name,
+                    /*isolate_data=*/nullptr, error));
+  if (isolate == nullptr) return nullptr;
+
+  auto source = spawning_group->source();
+  ASSERT(isolate->source() == source);
+
+  return isolate;
+#else
+  UNREACHABLE();
+#endif
+}
+
 Isolate* CreateWithinExistingIsolateGroup(IsolateGroup* group,
                                           const char* name,
                                           char** error) {
+#if !defined(DART_PRECOMPILED_RUNTIME)
   API_TIMELINE_DURATION(Thread::Current());
   CHECK_NO_ISOLATE(Isolate::Current());
 
@@ -1218,9 +1243,6 @@
   ASSERT(isolate->source() == source);
 
   if (source->script_kernel_buffer != nullptr) {
-#if defined(DART_PRECOMPILED_RUNTIME)
-    UNREACHABLE();
-#else
     Dart_EnterScope();
     {
       Thread* T = Thread::Current();
@@ -1250,7 +1272,6 @@
       isolate->object_store()->set_root_library(Library::Cast(tmp));
     }
     Dart_ExitScope();
-#endif  // defined(DART_PRECOMPILED_RUNTIME)
   }
 
   // If we are running in AppJIT training mode we'll have to remap class ids.
@@ -1321,7 +1342,9 @@
 
         isolate->isolate_group_ = group;
         group->RegisterIsolateLocked(isolate);
-        isolate->class_table()->shared_class_table_ = group->class_table();
+        isolate->class_table()->shared_class_table_ =
+            group->shared_class_table();
+        isolate->set_shared_class_table(group->shared_class_table());
 
         // Even though the mutator thread was descheduled, it will still
         // retain its [Thread] structure with valid isolate/isolate_group
@@ -1353,6 +1376,9 @@
   ASSERT(Thread::Current()->isolate_group() == isolate->group());
 
   return isolate;
+#else
+  UNREACHABLE();
+#endif
 }
 
 DART_EXPORT void Dart_IsolateFlagsInitialize(Dart_IsolateFlags* flags) {
diff --git a/runtime/vm/dart_api_impl.h b/runtime/vm/dart_api_impl.h
index 65ed764..16c875d 100644
--- a/runtime/vm/dart_api_impl.h
+++ b/runtime/vm/dart_api_impl.h
@@ -353,6 +353,9 @@
 Isolate* CreateWithinExistingIsolateGroup(IsolateGroup* group,
                                           const char* name,
                                           char** error);
+Isolate* CreateWithinExistingIsolateGroupAOT(IsolateGroup* group,
+                                             const char* name,
+                                             char** error);
 
 }  // namespace dart.
 
diff --git a/runtime/vm/dart_entry.cc b/runtime/vm/dart_entry.cc
index 42d53be..88a75f6 100644
--- a/runtime/vm/dart_entry.cc
+++ b/runtime/vm/dart_entry.cc
@@ -6,8 +6,6 @@
 
 #include "platform/safe_stack.h"
 #include "vm/class_finalizer.h"
-#include "vm/compiler/frontend/bytecode_reader.h"
-#include "vm/compiler/jit/compiler.h"
 #include "vm/debugger.h"
 #include "vm/dispatch_table.h"
 #include "vm/heap/safepoint.h"
@@ -19,6 +17,11 @@
 #include "vm/stub_code.h"
 #include "vm/symbols.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/frontend/bytecode_reader.h"
+#include "vm/compiler/jit/compiler.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 DECLARE_FLAG(bool, enable_interpreter);
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc
index d121a55..adb3591 100644
--- a/runtime/vm/debugger.cc
+++ b/runtime/vm/debugger.cc
@@ -8,13 +8,13 @@
 
 #include "platform/address_sanitizer.h"
 
+#include "vm/code_descriptors.h"
 #include "vm/code_patcher.h"
+#include "vm/compiler/api/deopt_id.h"
 #include "vm/compiler/assembler/disassembler.h"
 #include "vm/compiler/assembler/disassembler_kbc.h"
-#include "vm/compiler/frontend/bytecode_reader.h"
 #include "vm/compiler/jit/compiler.h"
 #include "vm/dart_entry.h"
-#include "vm/deopt_instructions.h"
 #include "vm/flags.h"
 #include "vm/globals.h"
 #include "vm/interpreter.h"
@@ -41,6 +41,11 @@
 #include "vm/token_position.h"
 #include "vm/visitor.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/frontend/bytecode_reader.h"
+#include "vm/deopt_instructions.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 DEFINE_FLAG(bool,
diff --git a/runtime/vm/debugger_x64.cc b/runtime/vm/debugger_x64.cc
index 883880f..3cd1810 100644
--- a/runtime/vm/debugger_x64.cc
+++ b/runtime/vm/debugger_x64.cc
@@ -8,7 +8,6 @@
 #include "vm/debugger.h"
 
 #include "vm/code_patcher.h"
-#include "vm/compiler/assembler/assembler.h"
 #include "vm/cpu.h"
 #include "vm/instructions.h"
 #include "vm/stub_code.h"
diff --git a/runtime/vm/deopt_instructions.h b/runtime/vm/deopt_instructions.h
index 926ea1b..57fff85 100644
--- a/runtime/vm/deopt_instructions.h
+++ b/runtime/vm/deopt_instructions.h
@@ -4,10 +4,10 @@
 
 #ifndef RUNTIME_VM_DEOPT_INSTRUCTIONS_H_
 #define RUNTIME_VM_DEOPT_INSTRUCTIONS_H_
+#if !defined(DART_PRECOMPILED_RUNTIME)
 
 #include "vm/allocation.h"
 #include "vm/code_descriptors.h"
-#include "vm/compiler/assembler/assembler.h"
 #include "vm/compiler/backend/flow_graph_compiler.h"
 #include "vm/compiler/backend/locations.h"
 #include "vm/deferred_objects.h"
@@ -19,6 +19,7 @@
 
 namespace dart {
 
+class Location;
 class Value;
 class MaterializeObjectInstr;
 class StackFrame;
@@ -615,4 +616,5 @@
 
 }  // namespace dart
 
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 #endif  // RUNTIME_VM_DEOPT_INSTRUCTIONS_H_
diff --git a/runtime/vm/exceptions.cc b/runtime/vm/exceptions.cc
index 762326e..76d0097 100644
--- a/runtime/vm/exceptions.cc
+++ b/runtime/vm/exceptions.cc
@@ -69,8 +69,9 @@
       : stacktrace_(StackTrace::Cast(stacktrace)),
         cur_index_(0),
         dropped_frames_(0) {
-    ASSERT(stacktrace_.raw() ==
-           Isolate::Current()->object_store()->preallocated_stack_trace());
+    ASSERT(
+        stacktrace_.raw() ==
+        Isolate::Current()->isolate_object_store()->preallocated_stack_trace());
   }
   ~PreallocatedStackTraceBuilder() {}
 
@@ -815,11 +816,12 @@
       ASSERT(incoming_exception.raw() ==
              isolate->object_store()->out_of_memory());
       const UnhandledException& error = UnhandledException::Handle(
-          zone, isolate->object_store()->preallocated_unhandled_exception());
+          zone,
+          isolate->isolate_object_store()->preallocated_unhandled_exception());
       thread->long_jump_base()->Jump(1, error);
       UNREACHABLE();
     }
-    stacktrace = isolate->object_store()->preallocated_stack_trace();
+    stacktrace = isolate->isolate_object_store()->preallocated_stack_trace();
     PreallocatedStackTraceBuilder frame_builder(stacktrace);
     ASSERT(existing_stacktrace.IsNull() ||
            (existing_stacktrace.raw() == stacktrace.raw()));
diff --git a/runtime/vm/ffi_callback_trampolines.cc b/runtime/vm/ffi_callback_trampolines.cc
index 6475a3d..4bf926e 100644
--- a/runtime/vm/ffi_callback_trampolines.cc
+++ b/runtime/vm/ffi_callback_trampolines.cc
@@ -3,11 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 
 #include "vm/ffi_callback_trampolines.h"
+
+#if !defined(DART_PRECOMPILED_RUNTIME)
 #include "vm/code_comments.h"
 #include "vm/code_observers.h"
 #include "vm/compiler/assembler/assembler.h"
 #include "vm/compiler/assembler/disassembler.h"
 #include "vm/exceptions.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
 namespace dart {
 
diff --git a/runtime/vm/ffi_callback_trampolines.h b/runtime/vm/ffi_callback_trampolines.h
index 7328828..26143be 100644
--- a/runtime/vm/ffi_callback_trampolines.h
+++ b/runtime/vm/ffi_callback_trampolines.h
@@ -1,15 +1,17 @@
 // Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
+#ifndef RUNTIME_VM_FFI_CALLBACK_TRAMPOLINES_H_
+#define RUNTIME_VM_FFI_CALLBACK_TRAMPOLINES_H_
 
 #include "platform/allocation.h"
 #include "platform/growable_array.h"
-#include "vm/compiler/stub_code_compiler.h"
 #include "vm/flag_list.h"
 #include "vm/virtual_memory.h"
 
-#ifndef RUNTIME_VM_FFI_CALLBACK_TRAMPOLINES_H_
-#define RUNTIME_VM_FFI_CALLBACK_TRAMPOLINES_H_
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/stub_code_compiler.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
 namespace dart {
 
diff --git a/runtime/vm/field_table.cc b/runtime/vm/field_table.cc
index 5a248c1..96d3f5a 100644
--- a/runtime/vm/field_table.cc
+++ b/runtime/vm/field_table.cc
@@ -95,7 +95,24 @@
   Thread::Current()->field_table_values_ = table_;
 }
 
+FieldTable* FieldTable::Clone() {
+  FieldTable* clone = new FieldTable();
+  auto new_table = static_cast<RawInstance**>(
+      malloc(capacity_ * sizeof(RawInstance*)));  // NOLINT
+  memmove(new_table, table_, top_ * sizeof(RawInstance*));
+  ASSERT(clone->table_ == nullptr);
+  clone->table_ = new_table;
+  clone->capacity_ = capacity_;
+  clone->top_ = top_;
+  return clone;
+}
+
 void FieldTable::VisitObjectPointers(ObjectPointerVisitor* visitor) {
+  // GC might try to visit field table before it's isolate done setting it up.
+  if (table_ == nullptr) {
+    return;
+  }
+
   ASSERT(visitor != NULL);
   visitor->set_gc_root_type("static fields table");
   visitor->VisitPointers(reinterpret_cast<RawObject**>(&table_[0]),
diff --git a/runtime/vm/field_table.h b/runtime/vm/field_table.h
index e89c733..08e83d8 100644
--- a/runtime/vm/field_table.h
+++ b/runtime/vm/field_table.h
@@ -55,6 +55,8 @@
   }
   void SetAt(intptr_t index, RawInstance* raw_instance);
 
+  FieldTable* Clone();
+
   void VisitObjectPointers(ObjectPointerVisitor* visitor);
 
   static const int kInitialCapacity = 512;
diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h
index f01d690..f1344a9 100644
--- a/runtime/vm/flag_list.h
+++ b/runtime/vm/flag_list.h
@@ -59,9 +59,8 @@
 // The syntax used is the same as that for FLAG_LIST below, as these flags are
 // automatically included in FLAG_LIST.
 #define VM_GLOBAL_FLAG_LIST(P, R, C, D)                                        \
-  P(dwarf_stack_traces, bool, false,                                           \
-    "Emit DWARF line number and inlining info"                                 \
-    "in dylib snapshots and don't symbolize stack traces.")                    \
+  P(dwarf_stack_traces_mode, bool, false,                                      \
+    "Use --[no-]dwarf-stack-traces instead.")                                  \
   P(causal_async_stacks, bool, !USING_PRODUCT, "Improved async stacks")        \
   P(lazy_async_stacks, bool, false, "Reconstruct async stacks from listeners") \
   P(use_bare_instructions, bool, true, "Enable bare instructions mode.")       \
@@ -195,9 +194,9 @@
   P(null_safety, bool, false,                                                  \
     "Respect the nullability of types in casts and instance checks.")          \
   P(use_table_dispatch, bool, true, "Enable dispatch table based calls.")      \
-  P(retain_dispatched_functions, bool, !USING_PRODUCT,                         \
-    "Serialize function objects for code in the dispatch table even if "       \
-    "not needed in the precompiled runtime")                                   \
+  P(retain_function_objects, bool, true,                                       \
+    "Serialize function objects for all code objects even if not otherwise "   \
+    "needed in the precompiled runtime.")                                      \
   P(enable_isolate_groups, bool, false, "Enable isolate group support.")       \
   P(show_invisible_frames, bool, false,                                        \
     "Show invisible frames in stack traces.")                                  \
diff --git a/runtime/vm/handles.h b/runtime/vm/handles.h
index 2b3de59..28ec9cc 100644
--- a/runtime/vm/handles.h
+++ b/runtime/vm/handles.h
@@ -207,6 +207,7 @@
 
   friend class HandleScope;
   friend class Dart;
+  friend class IsolateObjectStore;
   friend class ObjectStore;
   friend class ThreadState;
   DISALLOW_ALLOCATION();
diff --git a/runtime/vm/heap/heap.cc b/runtime/vm/heap/heap.cc
index 59abade..0403d54 100644
--- a/runtime/vm/heap/heap.cc
+++ b/runtime/vm/heap/heap.cc
@@ -87,15 +87,16 @@
   if (LIKELY(addr != 0)) {
     return addr;
   }
+  if (new_space_.GrowthControlState()) {
+    // This call to CollectGarbage might end up "reusing" a collection spawned
+    // from a different thread and will be racing to allocate the requested
+    // memory with other threads being released after the collection.
+    CollectGarbage(kNew);
 
-  // This call to CollectGarbage might end up "reusing" a collection spawned
-  // from a different thread and will be racing to allocate the requested
-  // memory with other threads being released after the collection.
-  CollectGarbage(kNew);
-
-  addr = new_space_.TryAllocate(thread, size);
-  if (LIKELY(addr != 0)) {
-    return addr;
+    addr = new_space_.TryAllocate(thread, size);
+    if (LIKELY(addr != 0)) {
+      return addr;
+    }
   }
 
   // It is possible a GC doesn't clear enough space.
@@ -651,14 +652,17 @@
 }
 
 void Heap::InitGrowthControl() {
+  new_space_.InitGrowthControl();
   old_space_.InitGrowthControl();
 }
 
 void Heap::SetGrowthControlState(bool state) {
+  new_space_.SetGrowthControlState(state);
   old_space_.SetGrowthControlState(state);
 }
 
 bool Heap::GrowthControlState() {
+  ASSERT(new_space_.GrowthControlState() == old_space_.GrowthControlState());
   return old_space_.GrowthControlState();
 }
 
@@ -720,6 +724,11 @@
 
 void Heap::CollectForDebugging() {
   if (gc_on_nth_allocation_ == kNoForcedGarbageCollection) return;
+  if (Thread::Current()->IsAtSafepoint()) {
+    // CollectAllGarbage is not supported when we are at a safepoint.
+    // Allocating when at a safepoint is not a common case.
+    return;
+  }
   gc_on_nth_allocation_--;
   if (gc_on_nth_allocation_ == 0) {
     CollectAllGarbage(kDebugging);
diff --git a/runtime/vm/heap/marker.cc b/runtime/vm/heap/marker.cc
index 69dc98b..e30b529 100644
--- a/runtime/vm/heap/marker.cc
+++ b/runtime/vm/heap/marker.cc
@@ -292,7 +292,7 @@
  public:
   explicit MarkingWeakVisitor(Thread* thread)
       : HandleVisitor(thread),
-        class_table_(thread->isolate_group()->class_table()) {}
+        class_table_(thread->isolate_group()->shared_class_table()) {}
 
   void VisitHandle(uword addr) {
     FinalizablePersistentHandle* handle =
diff --git a/runtime/vm/heap/pages.cc b/runtime/vm/heap/pages.cc
index a171b2f..16776bd 100644
--- a/runtime/vm/heap/pages.cc
+++ b/runtime/vm/heap/pages.cc
@@ -1113,7 +1113,7 @@
   const int64_t start = OS::GetCurrentMonotonicMicros();
 
   // Perform various cleanup that relies on no tasks interfering.
-  isolate_group->class_table()->FreeOldTables();
+  isolate_group->shared_class_table()->FreeOldTables();
   isolate_group->ForEachIsolate(
       [&](Isolate* isolate) { isolate->field_table()->FreeOldTables(); },
       /*at_safepoint=*/true);
diff --git a/runtime/vm/heap/safepoint.h b/runtime/vm/heap/safepoint.h
index 8615b53..7c3dde1 100644
--- a/runtime/vm/heap/safepoint.h
+++ b/runtime/vm/heap/safepoint.h
@@ -51,6 +51,8 @@
 
   void BlockForSafepoint(Thread* T);
 
+  bool IsOwnedByTheThread(Thread* thread) { return owner_ == thread; }
+
  private:
   void SafepointThreads(Thread* T);
   void ResumeThreads(Thread* T);
diff --git a/runtime/vm/heap/scavenger.cc b/runtime/vm/heap/scavenger.cc
index ee11b71..1ca8bd9 100644
--- a/runtime/vm/heap/scavenger.cc
+++ b/runtime/vm/heap/scavenger.cc
@@ -449,7 +449,7 @@
   ScavengerWeakVisitor(Thread* thread, Scavenger* scavenger)
       : HandleVisitor(thread),
         scavenger_(scavenger),
-        class_table_(thread->isolate_group()->class_table()) {
+        class_table_(thread->isolate_group()->shared_class_table()) {
     ASSERT(scavenger->heap_->isolate_group() == thread->isolate_group());
   }
 
diff --git a/runtime/vm/heap/scavenger.h b/runtime/vm/heap/scavenger.h
index d02d623..138a1a6 100644
--- a/runtime/vm/heap/scavenger.h
+++ b/runtime/vm/heap/scavenger.h
@@ -202,6 +202,16 @@
   void MakeNewSpaceIterable() const;
   int64_t FreeSpaceInWords(Isolate* isolate) const;
 
+  void InitGrowthControl() {
+    growth_control_ = true;
+  }
+
+  void SetGrowthControlState(bool state) {
+    growth_control_ = state;
+  }
+
+  bool GrowthControlState() { return growth_control_; }
+
   bool scavenging() const { return scavenging_; }
 
  private:
@@ -313,6 +323,8 @@
 
   bool failed_to_promote_;
 
+  bool growth_control_;
+
   // Protects new space during the allocation of new TLABs
   mutable Mutex space_lock_;
 
diff --git a/runtime/vm/heap/sweeper.cc b/runtime/vm/heap/sweeper.cc
index 2a19fba..96cf844 100644
--- a/runtime/vm/heap/sweeper.cc
+++ b/runtime/vm/heap/sweeper.cc
@@ -4,7 +4,6 @@
 
 #include "vm/heap/sweeper.h"
 
-#include "vm/compiler/assembler/assembler.h"
 #include "vm/globals.h"
 #include "vm/heap/freelist.h"
 #include "vm/heap/heap.h"
@@ -52,8 +51,7 @@
         uword cursor = current;
         uword end = current + obj_size;
         while (cursor < end) {
-          *reinterpret_cast<uword*>(cursor) =
-              compiler::Assembler::GetBreakInstructionFiller();
+          *reinterpret_cast<uword*>(cursor) = kBreakInstructionFiller;
           cursor += kWordSize;
         }
       } else {
diff --git a/runtime/vm/heap/weak_table.cc b/runtime/vm/heap/weak_table.cc
index b4cf28d..1bc9daf 100644
--- a/runtime/vm/heap/weak_table.cc
+++ b/runtime/vm/heap/weak_table.cc
@@ -135,7 +135,7 @@
 void WeakTable::MergeOtherWeakTable(WeakTable* other) {
   for (intptr_t i = 0; i < other->size(); i++) {
     if (other->IsValidEntryAtExclusive(i)) {
-      SetValue(other->ObjectAtExclusive(i), ValueIndex(i));
+      SetValueExclusive(other->ObjectAtExclusive(i), ValueIndex(i));
     }
   }
 }
diff --git a/runtime/vm/image_snapshot.cc b/runtime/vm/image_snapshot.cc
index e344120..01d5d36 100644
--- a/runtime/vm/image_snapshot.cc
+++ b/runtime/vm/image_snapshot.cc
@@ -6,7 +6,6 @@
 
 #include "platform/assert.h"
 #include "vm/class_id.h"
-#include "vm/compiler/backend/code_statistics.h"
 #include "vm/compiler/runtime_api.h"
 #include "vm/dwarf.h"
 #include "vm/elf.h"
@@ -22,6 +21,10 @@
 #include "vm/timeline.h"
 #include "vm/type_testing_stubs.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/backend/code_statistics.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 #if defined(DART_PRECOMPILER)
@@ -849,8 +852,7 @@
                 compiler::target::ObjectAlignment::kObjectAlignment) -
             unaligned_size;
         while (alignment_size > 0) {
-          WriteWordLiteralText(
-              compiler::Assembler::GetBreakInstructionFiller());
+          WriteWordLiteralText(kBreakInstructionFiller);
           alignment_size -= sizeof(compiler::target::uword);
           text_offset += sizeof(compiler::target::uword);
         }
diff --git a/runtime/vm/interpreter.cc b/runtime/vm/interpreter.cc
index cb8c0e3..bc009ec 100644
--- a/runtime/vm/interpreter.cc
+++ b/runtime/vm/interpreter.cc
@@ -10,6 +10,7 @@
 
 #include "vm/interpreter.h"
 
+#include "vm/compiler/api/type_check_mode.h"
 #include "vm/compiler/assembler/assembler.h"
 #include "vm/compiler/assembler/disassembler_kbc.h"
 #include "vm/compiler/backend/flow_graph_compiler.h"
@@ -272,7 +273,7 @@
 
   const uword start = thread->top();
 #ifndef PRODUCT
-  auto table = thread->isolate_group()->class_table();
+  auto table = thread->isolate_group()->shared_class_table();
   if (UNLIKELY(table->TraceAllocationFor(class_id))) {
     return false;
   }
diff --git a/runtime/vm/intrusive_dlist.h b/runtime/vm/intrusive_dlist.h
index 45c4365..421726d64 100644
--- a/runtime/vm/intrusive_dlist.h
+++ b/runtime/vm/intrusive_dlist.h
@@ -108,20 +108,20 @@
     prev_ = nullptr;
   }
 
-  bool IsEmpty() {
+  bool IsEmpty() const {
     bool result = next_ == this;
     ASSERT(result == (prev_ == this));
     return result;
   }
 
-  bool IsLinked() {
+  bool IsLinked() const {
     ASSERT((next_ == nullptr) == (prev_ == nullptr));
     return next_ != nullptr;
   }
 
-  IntrusiveDListEntry<T, N>* Prev() { return prev_; }
+  IntrusiveDListEntry<T, N>* Prev() const { return prev_; }
 
-  IntrusiveDListEntry<T, N>* Next() { return next_; }
+  IntrusiveDListEntry<T, N>* Next() const { return next_; }
 
   friend class IntrusiveDList<T, N>;
 
@@ -143,9 +143,9 @@
              IntrusiveDListEntry<ContainerType, I>* entry)
         : head_(head), entry_(entry) {}
 
-    inline ContainerType* operator->() { return entry_->container(); }
+    inline ContainerType* operator->() const { return entry_->container(); }
 
-    inline ContainerType* operator*() { return entry_->container(); }
+    inline ContainerType* operator*() const { return entry_->container(); }
 
     inline bool operator==(const Iterator<ContainerType, I>& other) const {
       return entry_ == other.entry_;
@@ -180,18 +180,18 @@
 
   // NOTE: This function only checks whether [a] is linked inside *a*
   // [IntrusiveDList].
-  inline bool IsInList(T* a) { return convert(a)->IsLinked(); }
+  inline bool IsInList(T* a) const { return convert(a)->IsLinked(); }
 
   inline void Remove(T* a) { convert(a)->Remove(); }
 
-  inline bool IsEmpty() { return head_.IsEmpty(); }
+  inline bool IsEmpty() const { return head_.IsEmpty(); }
 
-  inline T* First() {
+  inline T* First() const {
     ASSERT(!IsEmpty());
     return head_.Next()->container();
   }
 
-  inline T* Last() {
+  inline T* Last() const {
     ASSERT(!IsEmpty());
     return head_.Prev()->container();
   }
@@ -230,7 +230,7 @@
  private:
   Entry head_;
 
-  Entry* convert(T* entry) { return static_cast<Entry*>(entry); }
+  Entry* convert(T* entry) const { return static_cast<Entry*>(entry); }
 };
 
 }  // namespace dart.
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index bd0db66..46fe80d 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -221,7 +221,8 @@
 };
 
 IsolateGroup::IsolateGroup(std::shared_ptr<IsolateGroupSource> source,
-                           void* embedder_data)
+                           void* embedder_data,
+                           ObjectStore* object_store)
     : embedder_data_(embedder_data),
       isolates_lock_(new SafepointRwLock()),
       isolates_(),
@@ -234,14 +235,37 @@
       thread_registry_(new ThreadRegistry()),
       safepoint_handler_(new SafepointHandler(this)),
       shared_class_table_(new SharedClassTable()),
+      object_store_(object_store),
+#if defined(DART_PRECOMPILED_RUNTIME)
+      class_table_(new ClassTable(shared_class_table_.get())),
+#else
+      class_table_(nullptr),
+#endif
+      symbols_lock_(new SafepointRwLock()),
       store_buffer_(new StoreBuffer()),
-      heap_(nullptr) {
+      heap_(nullptr),
+      saved_unlinked_calls_(Array::null()) {
   {
     WriteRwLocker wl(ThreadState::Current(), isolate_groups_rwlock_);
     id_ = isolate_group_random_->NextUInt64();
   }
 }
 
+IsolateGroup::IsolateGroup(std::shared_ptr<IsolateGroupSource> source,
+                           void* embedder_data)
+    : IsolateGroup(source,
+                   embedder_data,
+#if !defined(DART_PRECOMPILED_RUNTIME)
+                   // in JIT, with --enable_isolate_groups keep object store
+                   // on isolate, rather than on isolate group
+                   FLAG_enable_isolate_groups ? nullptr :
+#endif
+                                              new ObjectStore()) {
+  if (object_store() != nullptr) {
+    object_store()->InitStubs();
+  }
+}
+
 IsolateGroup::~IsolateGroup() {
   // Finalize any weak persistent handles with a non-null referent.
   FinalizeWeakPersistentHandlesVisitor visitor(this);
@@ -337,6 +361,10 @@
   heap_ = std::move(heap);
 }
 
+void IsolateGroup::set_saved_unlinked_calls(const Array& saved_unlinked_calls) {
+  saved_unlinked_calls_ = saved_unlinked_calls.raw();
+}
+
 Thread* IsolateGroup::ScheduleThreadLocked(MonitorLocker* ml,
                                            Thread* existing_mutator_thread,
                                            bool is_vm_isolate,
@@ -694,6 +722,13 @@
       writer.WriteMessage(msg, main_port(), Message::kOOBPriority));
 }
 
+void Isolate::set_object_store(ObjectStore* object_store) {
+  ASSERT(cached_object_store_ == nullptr);
+  object_store_shared_ptr_.reset(object_store);
+  cached_object_store_ = object_store;
+  isolate_object_store_->set_object_store(object_store);
+}
+
 class IsolateMessageHandler : public MessageHandler {
  public:
   explicit IsolateMessageHandler(Isolate* isolate);
@@ -1329,9 +1364,17 @@
       current_tag_(UserTag::null()),
       default_tag_(UserTag::null()),
       ic_miss_code_(Code::null()),
-      class_table_(isolate_group->class_table()),
+      shared_class_table_(isolate_group->shared_class_table()),
       field_table_(new FieldTable()),
       isolate_group_(isolate_group),
+      isolate_object_store_(
+          new IsolateObjectStore(isolate_group->object_store())),
+      object_store_shared_ptr_(isolate_group->object_store_shared_ptr()),
+#if defined(DART_PRECOMPILED_RUNTIME)
+      class_table_(isolate_group->class_table_shared_ptr()),
+#else
+      class_table_(new ClassTable(shared_class_table_)),
+#endif
 #if !defined(DART_PRECOMPILED_RUNTIME)
       native_callback_trampolines_(),
 #endif
@@ -1352,7 +1395,7 @@
       start_time_micros_(OS::GetCurrentMonotonicMicros()),
       random_(),
       mutex_(NOT_IN_PRODUCT("Isolate::mutex_")),
-      symbols_mutex_(NOT_IN_PRODUCT("Isolate::symbols_mutex_")),
+      symbols_lock_(new SafepointRwLock()),
       type_canonicalization_mutex_(
           NOT_IN_PRODUCT("Isolate::type_canonicalization_mutex_")),
       constant_canonicalization_mutex_(
@@ -1373,6 +1416,8 @@
       spawn_count_monitor_(),
       handler_info_cache_(),
       catch_entry_moves_cache_() {
+  cached_object_store_ = object_store_shared_ptr_.get();
+  cached_class_table_table_ = class_table_->table();
   FlagsCopyFrom(api_flags);
   SetErrorsFatal(true);
   // TODO(asiva): A Thread is not available here, need to figure out
@@ -1407,9 +1452,6 @@
   delete reverse_pc_lookup_cache_;
   reverse_pc_lookup_cache_ = nullptr;
 
-  delete dispatch_table_;
-  dispatch_table_ = nullptr;
-
   if (FLAG_enable_interpreter) {
     delete background_compiler_;
     background_compiler_ = nullptr;
@@ -1428,7 +1470,6 @@
 #endif  // !defined(PRODUCT)
 
   free(name_);
-  delete object_store_;
   delete field_table_;
 #if defined(USING_SIMULATOR)
   delete simulator_;
@@ -1485,6 +1526,21 @@
                               bool is_vm_isolate) {
   Isolate* result = new Isolate(isolate_group, api_flags);
   result->BuildName(name_prefix);
+  if (!is_vm_isolate) {
+    // vm isolate object store is initialized later, after null instance
+    // is created (in Dart::Init).
+    // Non-vm isolates need to have isolate object store initialized is that
+    // exit_listeners have to be null-initialized as they will be used if
+    // we fail to create isolate below, have to do low level shutdown.
+    if (result->object_store() == nullptr) {
+      // in JIT with --enable-isolate-groups each isolate still
+      // has to have its own object store
+      result->set_object_store(new ObjectStore());
+      result->object_store()->InitStubs();
+    }
+    result->isolate_object_store()->Init();
+  }
+
   ASSERT(result != nullptr);
 
 #if !defined(PRODUCT)
@@ -1608,6 +1664,17 @@
   return OS::GetCurrentMonotonicMicros() - start_time_micros_;
 }
 
+Dart_Port Isolate::origin_id() {
+  MutexLocker ml(&origin_id_mutex_);
+  return origin_id_;
+}
+
+void Isolate::set_origin_id(Dart_Port id) {
+  MutexLocker ml(&origin_id_mutex_);
+  ASSERT((id == main_port_ && origin_id_ == 0) || (origin_id_ == main_port_));
+  origin_id_ = id;
+}
+
 bool Isolate::IsPaused() const {
 #if defined(PRODUCT)
   return false;
@@ -1664,7 +1731,7 @@
   RELEASE_ASSERT(isolates_.First() == isolates_.Last());
   RELEASE_ASSERT(isolates_.First() == Isolate::Current());
 
-  auto shared_class_table = IsolateGroup::Current()->class_table();
+  auto shared_class_table = IsolateGroup::Current()->shared_class_table();
   std::shared_ptr<IsolateGroupReloadContext> group_reload_context(
       new IsolateGroupReloadContext(this, shared_class_table, js));
   group_reload_context_ = group_reload_context;
@@ -1697,7 +1764,7 @@
   RELEASE_ASSERT(isolates_.First() == isolates_.Last());
   RELEASE_ASSERT(isolates_.First() == Isolate::Current());
 
-  auto shared_class_table = IsolateGroup::Current()->class_table();
+  auto shared_class_table = IsolateGroup::Current()->shared_class_table();
   std::shared_ptr<IsolateGroupReloadContext> group_reload_context(
       new IsolateGroupReloadContext(this, shared_class_table, js));
   group_reload_context_ = group_reload_context;
@@ -1802,7 +1869,7 @@
       compiler::target::kSmiMax / (6 * kWordSize);
 
   const GrowableObjectArray& caps = GrowableObjectArray::Handle(
-      current_zone(), object_store()->resume_capabilities());
+      current_zone(), isolate_object_store()->resume_capabilities());
   Capability& current = Capability::Handle(current_zone());
   intptr_t insertion_index = -1;
   for (intptr_t i = 0; i < caps.Length(); i++) {
@@ -1831,7 +1898,7 @@
 
 bool Isolate::RemoveResumeCapability(const Capability& capability) {
   const GrowableObjectArray& caps = GrowableObjectArray::Handle(
-      current_zone(), object_store()->resume_capabilities());
+      current_zone(), isolate_object_store()->resume_capabilities());
   Capability& current = Capability::Handle(current_zone());
   for (intptr_t i = 0; i < caps.Length(); i++) {
     current ^= caps.At(i);
@@ -1854,7 +1921,7 @@
       compiler::target::kSmiMax / (12 * kWordSize);
 
   const GrowableObjectArray& listeners = GrowableObjectArray::Handle(
-      current_zone(), object_store()->exit_listeners());
+      current_zone(), isolate_object_store()->exit_listeners());
   SendPort& current = SendPort::Handle(current_zone());
   intptr_t insertion_index = -1;
   for (intptr_t i = 0; i < listeners.Length(); i += 2) {
@@ -1885,7 +1952,7 @@
 
 void Isolate::RemoveExitListener(const SendPort& listener) {
   const GrowableObjectArray& listeners = GrowableObjectArray::Handle(
-      current_zone(), object_store()->exit_listeners());
+      current_zone(), isolate_object_store()->exit_listeners());
   SendPort& current = SendPort::Handle(current_zone());
   for (intptr_t i = 0; i < listeners.Length(); i += 2) {
     current ^= listeners.At(i);
@@ -1901,7 +1968,7 @@
 
 void Isolate::NotifyExitListeners() {
   const GrowableObjectArray& listeners = GrowableObjectArray::Handle(
-      current_zone(), this->object_store()->exit_listeners());
+      current_zone(), isolate_object_store()->exit_listeners());
   if (listeners.IsNull()) return;
 
   SendPort& listener = SendPort::Handle(current_zone());
@@ -1922,7 +1989,7 @@
       compiler::target::kSmiMax / (6 * kWordSize);
 
   const GrowableObjectArray& listeners = GrowableObjectArray::Handle(
-      current_zone(), object_store()->error_listeners());
+      current_zone(), isolate_object_store()->error_listeners());
   SendPort& current = SendPort::Handle(current_zone());
   intptr_t insertion_index = -1;
   for (intptr_t i = 0; i < listeners.Length(); i++) {
@@ -1950,7 +2017,7 @@
 
 void Isolate::RemoveErrorListener(const SendPort& listener) {
   const GrowableObjectArray& listeners = GrowableObjectArray::Handle(
-      current_zone(), object_store()->error_listeners());
+      current_zone(), isolate_object_store()->error_listeners());
   SendPort& current = SendPort::Handle(current_zone());
   for (intptr_t i = 0; i < listeners.Length(); i++) {
     current ^= listeners.At(i);
@@ -1966,7 +2033,7 @@
 bool Isolate::NotifyErrorListeners(const String& msg,
                                    const String& stacktrace) {
   const GrowableObjectArray& listeners = GrowableObjectArray::Handle(
-      current_zone(), this->object_store()->error_listeners());
+      current_zone(), isolate_object_store()->error_listeners());
   if (listeners.IsNull()) return false;
 
   const Array& arr = Array::Handle(current_zone(), Array::New(2));
@@ -2388,16 +2455,23 @@
                                   ValidationPolicy validate_frames) {
   ASSERT(visitor != nullptr);
 
-  // Visit objects in the object store.
-  if (object_store() != nullptr) {
+  // Visit objects in the object store if there is no isolate group object store
+  if (group()->object_store() == nullptr && object_store() != nullptr) {
     object_store()->VisitObjectPointers(visitor);
   }
+  // Visit objects in the isolate object store.
+  if (isolate_object_store() != nullptr) {
+    isolate_object_store()->VisitObjectPointers(visitor);
+  }
 
   // Visit objects in the class table.
   class_table()->VisitObjectPointers(visitor);
 
   // Visit objects in the field table.
   field_table()->VisitObjectPointers(visitor);
+  if (saved_initial_field_table() != nullptr) {
+    saved_initial_field_table()->VisitObjectPointers(visitor);
+  }
 
   visitor->clear_gc_root_type();
   // Visit the objects directly referenced from the isolate structure.
@@ -2511,6 +2585,11 @@
   }
 }
 
+Isolate* IsolateGroup::FirstIsolate() const {
+  SafepointWriteRwLocker ml(Thread::Current(), isolates_lock_.get());
+  return isolates_.IsEmpty() ? nullptr : isolates_.First();
+}
+
 void IsolateGroup::RunWithStoppedMutators(
     std::function<void()> single_current_mutator,
     std::function<void()> otherwise,
@@ -2522,6 +2601,12 @@
     return;
   }
 
+  if (thread->IsAtSafepoint() &&
+      safepoint_handler()->IsOwnedByTheThread(thread)) {
+    single_current_mutator();
+    return;
+  }
+
   {
     SafepointReadRwLocker ml(thread, isolates_lock_.get());
     const bool only_one_isolate = isolates_.First() == isolates_.Last();
@@ -2551,6 +2636,11 @@
       },
       /*at_safepoint=*/true);
   api_state()->VisitObjectPointersUnlocked(visitor);
+  // Visit objects in the object store.
+  if (object_store() != nullptr) {
+    object_store()->VisitObjectPointers(visitor);
+  }
+  visitor->VisitPointer(reinterpret_cast<RawObject**>(&saved_unlinked_calls_));
   VisitStackPointers(visitor, validate_frames);
 }
 
@@ -2624,10 +2714,10 @@
   if (IsReloading()) {
     return group_reload_context_->GetClassSizeForHeapWalkAt(cid);
   } else {
-    return class_table()->SizeAt(cid);
+    return shared_class_table()->SizeAt(cid);
   }
 #else
-  return class_table()->SizeAt(cid);
+  return shared_class_table()->SizeAt(cid);
 #endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
 }
 
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index 621dfd2..eaaf89c 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -19,8 +19,8 @@
 #include "vm/base_isolate.h"
 #include "vm/class_table.h"
 #include "vm/constants_kbc.h"
+#include "vm/dispatch_table.h"
 #include "vm/exceptions.h"
-#include "vm/ffi_callback_trampolines.h"
 #include "vm/field_table.h"
 #include "vm/fixed_cache.h"
 #include "vm/growable_array.h"
@@ -37,6 +37,10 @@
 #include "vm/token_position.h"
 #include "vm/virtual_memory.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/ffi_callback_trampolines.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 // Forward declarations.
@@ -46,7 +50,6 @@
 class CodeIndexTable;
 class Debugger;
 class DeoptContext;
-class DispatchTable;
 class ExternalTypedData;
 class HandleScope;
 class HandleVisitor;
@@ -55,6 +58,7 @@
 #if !defined(DART_PRECOMPILED_RUNTIME)
 class Interpreter;
 #endif
+class IsolateObjectStore;
 class IsolateProfilerData;
 class IsolateReloadContext;
 class IsolateSpawnState;
@@ -275,6 +279,9 @@
 // Represents an isolate group and is shared among all isolates within a group.
 class IsolateGroup : public IntrusiveDListEntry<IsolateGroup> {
  public:
+  IsolateGroup(std::shared_ptr<IsolateGroupSource> source,
+               void* embedder_data,
+               ObjectStore* object_store);
   IsolateGroup(std::shared_ptr<IsolateGroupSource> source, void* embedder_data);
   ~IsolateGroup();
 
@@ -333,8 +340,18 @@
   }
 #endif  // !defined(PRODUCT)
 
-  SharedClassTable* class_table() const { return shared_class_table_.get(); }
+  DispatchTable* dispatch_table() const { return dispatch_table_.get(); }
+  void set_dispatch_table(DispatchTable* table) {
+    dispatch_table_.reset(table);
+  }
+
+  SharedClassTable* shared_class_table() const {
+    return shared_class_table_.get();
+  }
   StoreBuffer* store_buffer() const { return store_buffer_.get(); }
+  ClassTable* class_table() const { return class_table_.get(); }
+  ObjectStore* object_store() const { return object_store_.get(); }
+  SafepointRwLock* symbols_lock() { return symbols_lock_.get(); }
 
   static inline IsolateGroup* Current() {
     Thread* thread = Thread::Current();
@@ -386,6 +403,7 @@
   // adding/removing isolates, so no locks will be held.
   void ForEachIsolate(std::function<void(Isolate* isolate)> function,
                       bool at_safepoint = false);
+  Isolate* FirstIsolate() const;
 
   // Ensures mutators are stopped during execution of the provided function.
   //
@@ -401,8 +419,9 @@
                               std::function<void()> otherwise,
                               bool use_force_growth_in_otherwise = false);
 
-  void RunWithStoppedMutators(std::function<void()> function) {
-    RunWithStoppedMutators(function, function);
+  void RunWithStoppedMutators(std::function<void()> function,
+                              bool use_force_growth = false) {
+    RunWithStoppedMutators(function, function, use_force_growth);
   }
 
 #ifndef PRODUCT
@@ -483,9 +502,14 @@
   void RememberLiveTemporaries();
   void DeferredMarkLiveTemporaries();
 
+  RawArray* saved_unlinked_calls() const { return saved_unlinked_calls_; }
+  void set_saved_unlinked_calls(const Array& saved_unlinked_calls);
+
  private:
   friend class Heap;
   friend class StackFrame;  // For `[isolates_].First()`.
+  // For `object_store_shared_ptr()`, `class_table_shared_ptr()`
+  friend class Isolate;
 
 #define ISOLATE_GROUP_FLAG_BITS(V) V(CompactionInProgress)
 
@@ -503,6 +527,13 @@
 
   void set_heap(std::unique_ptr<Heap> value);
 
+  const std::shared_ptr<ClassTable>& class_table_shared_ptr() const {
+    return class_table_;
+  }
+  const std::shared_ptr<ObjectStore>& object_store_shared_ptr() const {
+    return object_store_;
+  }
+
   bool is_vm_isolate_heap_ = false;
   void* embedder_data_ = nullptr;
 
@@ -544,8 +575,17 @@
   uint64_t id_ = 0;
 
   std::unique_ptr<SharedClassTable> shared_class_table_;
+  std::shared_ptr<ObjectStore> object_store_;  // nullptr in JIT mode
+  std::shared_ptr<ClassTable> class_table_;    // nullptr in JIT mode
+  // This symbols_mutex_ on Isolate is only used when IsolateGroup does not
+  // have object_store.
+  std::unique_ptr<SafepointRwLock>
+      symbols_lock_;  // Protects concurrent access to the symbol table.
   std::unique_ptr<StoreBuffer> store_buffer_;
   std::unique_ptr<Heap> heap_;
+  std::unique_ptr<DispatchTable> dispatch_table_;
+  RawArray* saved_unlinked_calls_;
+
   IdleTimeHandler idle_time_handler_;
   uint32_t isolate_group_flags_ = 0;
 };
@@ -604,12 +644,53 @@
     return group()->safepoint_handler();
   }
 
-  ClassTable* class_table() { return &class_table_; }
-  static intptr_t class_table_offset() {
-    return OFFSET_OF(Isolate, class_table_);
+  ClassTable* class_table() { return class_table_.get(); }
+
+  RawClass** cached_class_table_table() { return cached_class_table_table_; }
+  void set_cached_class_table_table(RawClass** cached_class_table_table) {
+    cached_class_table_table_ = cached_class_table_table;
+  }
+  static intptr_t cached_class_table_table_offset() {
+    return OFFSET_OF(Isolate, cached_class_table_table_);
   }
 
+  SharedClassTable* shared_class_table() const { return shared_class_table_; }
+  // Used during isolate creation to re-register isolate with right group.
+  void set_shared_class_table(SharedClassTable* table) {
+    shared_class_table_ = table;
+  }
+  // Used by the generated code.
+  static intptr_t shared_class_table_offset() {
+    return OFFSET_OF(Isolate, shared_class_table_);
+  }
+
+  ObjectStore* object_store() const { return object_store_shared_ptr_.get(); }
+  void set_object_store(ObjectStore* object_store);
+  static intptr_t cached_object_store_offset() {
+    return OFFSET_OF(Isolate, cached_object_store_);
+  }
+  SafepointRwLock* symbols_lock() { return symbols_lock_.get(); }
+
   FieldTable* field_table() const { return field_table_; }
+  void set_field_table(Thread* T, FieldTable* field_table) {
+    delete field_table_;
+    field_table_ = field_table;
+    T->field_table_values_ = field_table->table();
+  }
+
+  FieldTable* saved_initial_field_table() const {
+    return saved_initial_field_table_.get();
+  }
+  std::shared_ptr<FieldTable> saved_initial_field_table_shareable() {
+    return saved_initial_field_table_;
+  }
+  void set_saved_initial_field_table(std::shared_ptr<FieldTable> field_table) {
+    saved_initial_field_table_ = field_table;
+  }
+
+  IsolateObjectStore* isolate_object_store() const {
+    return isolate_object_store_.get();
+  }
 
   // Prefers old classes when we are in the middle of a reload.
   RawClass* GetClassForHeapWalkAt(intptr_t cid);
@@ -643,11 +724,8 @@
     ASSERT(main_port_ == 0);  // Only set main port once.
     main_port_ = port;
   }
-  Dart_Port origin_id() const { return origin_id_; }
-  void set_origin_id(Dart_Port id) {
-    ASSERT((id == main_port_ && origin_id_ == 0) || (origin_id_ == main_port_));
-    origin_id_ = id;
-  }
+  Dart_Port origin_id();
+  void set_origin_id(Dart_Port id);
   void set_pause_capability(uint64_t value) { pause_capability_ = value; }
   uint64_t pause_capability() const { return pause_capability_; }
   void set_terminate_capability(uint64_t value) {
@@ -659,12 +737,6 @@
 
   Heap* heap() const { return isolate_group_->heap(); }
 
-  ObjectStore* object_store() const { return object_store_; }
-  void set_object_store(ObjectStore* value) { object_store_ = value; }
-  static intptr_t object_store_offset() {
-    return OFFSET_OF(Isolate, object_store_);
-  }
-
   void set_init_callback_data(void* value) { init_callback_data_ = value; }
   void* init_callback_data() const { return init_callback_data_; }
 
@@ -714,7 +786,6 @@
   }
 
   Mutex* mutex() { return &mutex_; }
-  Mutex* symbols_mutex() { return &symbols_mutex_; }
   Mutex* type_canonicalization_mutex() { return &type_canonicalization_mutex_; }
   Mutex* constant_canonicalization_mutex() {
     return &constant_canonicalization_mutex_;
@@ -732,7 +803,6 @@
 
 #if !defined(PRODUCT)
   Debugger* debugger() const {
-    ASSERT(debugger_ != nullptr);
     return debugger_;
   }
 
@@ -1045,8 +1115,9 @@
   void set_obfuscation_map(const char** map) { obfuscation_map_ = map; }
   const char** obfuscation_map() const { return obfuscation_map_; }
 
-  const DispatchTable* dispatch_table() const { return dispatch_table_; }
-  void set_dispatch_table(DispatchTable* table) { dispatch_table_ = table; }
+  const DispatchTable* dispatch_table() const {
+    return group()->dispatch_table();
+  }
 
   // Returns the pc -> code lookup cache object for this isolate.
   ReversePcLookupCache* reverse_pc_lookup_cache() const {
@@ -1230,14 +1301,23 @@
   RawUserTag* current_tag_;
   RawUserTag* default_tag_;
   RawCode* ic_miss_code_;
-  ObjectStore* object_store_ = nullptr;
-  ClassTable class_table_;
+  // Cached value of object_store_shared_ptr_, here for generated code access
+  ObjectStore* cached_object_store_ = nullptr;
+  SharedClassTable* shared_class_table_ = nullptr;
+  // Cached value of class_table_->table_, here for generated code access
+  RawClass** cached_class_table_table_ = nullptr;
   FieldTable* field_table_ = nullptr;
   bool single_step_ = false;
   // End accessed from generated code.
 
   IsolateGroup* isolate_group_;
   IdleTimeHandler idle_time_handler_;
+  std::shared_ptr<FieldTable> saved_initial_field_table_;
+  std::unique_ptr<IsolateObjectStore> isolate_object_store_;
+  // shared in AOT(same pointer as on IsolateGroup), not shared in JIT
+  std::shared_ptr<ObjectStore> object_store_shared_ptr_;
+  // shared in AOT(same pointer as on IsolateGroup), not shared in JIT
+  std::shared_ptr<ClassTable> class_table_;
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
   NativeCallbackTrampolines native_callback_trampolines_;
@@ -1325,6 +1405,7 @@
   Dart_Port main_port_ = 0;
   // Isolates created by Isolate.spawn have the same origin id.
   Dart_Port origin_id_ = 0;
+  Mutex origin_id_mutex_;
   uint64_t pause_capability_ = 0;
   uint64_t terminate_capability_ = 0;
   void* init_callback_data_ = nullptr;
@@ -1332,7 +1413,8 @@
   Random random_;
   Simulator* simulator_ = nullptr;
   Mutex mutex_;          // Protects compiler stats.
-  Mutex symbols_mutex_;  // Protects concurrent access to the symbol table.
+  std::unique_ptr<SafepointRwLock>
+      symbols_lock_;  // Protects concurrent access to the symbol table.
   Mutex type_canonicalization_mutex_;      // Protects type canonicalization.
   Mutex constant_canonicalization_mutex_;  // Protects const canonicalization.
   Mutex megamorphic_mutex_;  // Protects the table of megamorphic caches and
diff --git a/runtime/vm/kernel.cc b/runtime/vm/kernel.cc
index 1c1c752..5e69eac 100644
--- a/runtime/vm/kernel.cc
+++ b/runtime/vm/kernel.cc
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+
 #include "vm/kernel.h"
 
 #include "vm/bit_vector.h"
@@ -14,7 +16,6 @@
 #include "vm/parser.h"  // For Parser::kParameter* constants.
 #include "vm/stack_frame.h"
 
-#if !defined(DART_PRECOMPILED_RUNTIME)
 
 namespace dart {
 namespace kernel {
diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc
index b36b2a9..754d2e5 100644
--- a/runtime/vm/kernel_loader.cc
+++ b/runtime/vm/kernel_loader.cc
@@ -1,6 +1,7 @@
 // Copyright (c) 2016, 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.
+#if !defined(DART_PRECOMPILED_RUNTIME)
 
 #include "vm/kernel_loader.h"
 
@@ -23,7 +24,6 @@
 #include "vm/symbols.h"
 #include "vm/thread.h"
 
-#if !defined(DART_PRECOMPILED_RUNTIME)
 namespace dart {
 namespace kernel {
 
diff --git a/runtime/vm/native_entry.cc b/runtime/vm/native_entry.cc
index 3690222..47582a1 100644
--- a/runtime/vm/native_entry.cc
+++ b/runtime/vm/native_entry.cc
@@ -310,9 +310,14 @@
       const Code& current_trampoline =
           Code::Handle(zone, CodePatcher::GetNativeCallAt(
                                  caller_frame->pc(), code, &current_function));
+      // Some other isolate(with code being shared in AOT) might have updated
+      // target function/trampoline already.
       ASSERT(current_function ==
-             reinterpret_cast<NativeFunction>(LinkNativeCall));
-      ASSERT(current_trampoline.raw() == StubCode::CallBootstrapNative().raw());
+                 reinterpret_cast<NativeFunction>(LinkNativeCall) ||
+             current_function == target_function);
+      ASSERT(current_trampoline.raw() ==
+                 StubCode::CallBootstrapNative().raw() ||
+             current_function == target_function);
     }
 #endif
 
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 6df21cb..aac5f8b 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -13,17 +13,10 @@
 #include "vm/bootstrap.h"
 #include "vm/class_finalizer.h"
 #include "vm/code_comments.h"
+#include "vm/code_descriptors.h"
 #include "vm/code_observers.h"
-#include "vm/compiler/aot/precompiler.h"
-#include "vm/compiler/assembler/assembler.h"
 #include "vm/compiler/assembler/disassembler.h"
 #include "vm/compiler/assembler/disassembler_kbc.h"
-#include "vm/compiler/compiler_state.h"
-#include "vm/compiler/frontend/bytecode_fingerprints.h"
-#include "vm/compiler/frontend/bytecode_reader.h"
-#include "vm/compiler/frontend/kernel_fingerprints.h"
-#include "vm/compiler/frontend/kernel_translation_helper.h"
-#include "vm/compiler/intrinsifier.h"
 #include "vm/compiler/jit/compiler.h"
 #include "vm/cpu.h"
 #include "vm/dart.h"
@@ -63,6 +56,18 @@
 #include "vm/type_testing_stubs.h"
 #include "vm/zone_text_buffer.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/aot/precompiler.h"
+#include "vm/compiler/assembler/assembler.h"
+#include "vm/compiler/backend/code_statistics.h"
+#include "vm/compiler/compiler_state.h"
+#include "vm/compiler/frontend/bytecode_fingerprints.h"
+#include "vm/compiler/frontend/bytecode_reader.h"
+#include "vm/compiler/frontend/kernel_fingerprints.h"
+#include "vm/compiler/frontend/kernel_translation_helper.h"
+#include "vm/compiler/intrinsifier.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 DEFINE_FLAG(int,
@@ -1631,6 +1636,8 @@
 #if !defined(DART_PRECOMPILED_RUNTIME)
     // Object::Init version when we are bootstrapping from source or from a
     // Kernel binary.
+    // This will initialize isolate group object_store, shared by all isolates
+    // running in the isolate group.
     ObjectStore* object_store = isolate->object_store();
 
     Class& cls = Class::Handle(zone);
@@ -1642,6 +1649,7 @@
     // All RawArray fields will be initialized to an empty array, therefore
     // initialize array class first.
     cls = Class::New<Array, RTN::Array>(isolate);
+    ASSERT(object_store->array_class() == Class::null());
     object_store->set_array_class(cls);
 
     // VM classes that are parameterized (Array, ImmutableArray,
@@ -2530,8 +2538,7 @@
   uword cur = address + sizeof(RawObject);
   uword end = address + size;
   if (class_id == kInstructionsCid) {
-    compiler::target::uword initial_value =
-        compiler::Assembler::GetBreakInstructionFiller();
+    compiler::target::uword initial_value = kBreakInstructionFiller;
     while (cur < end) {
       *reinterpret_cast<compiler::target::uword*>(cur) = initial_value;
       cur += compiler::target::kWordSize;
@@ -2630,7 +2637,7 @@
     }
   }
 #ifndef PRODUCT
-  auto class_table = thread->isolate_group()->class_table();
+  auto class_table = thread->isolate_group()->shared_class_table();
   if (class_table->TraceAllocationFor(cls_id)) {
     Profiler::SampleAllocation(thread, cls_id);
   }
@@ -3287,9 +3294,10 @@
     set_num_native_fields(super.num_native_fields());
 
     if (FLAG_precompiled_mode) {
-      host_bitmap =
-          Isolate::Current()->group()->class_table()->GetUnboxedFieldsMapAt(
-              super.id());
+      host_bitmap = Isolate::Current()
+                        ->group()
+                        ->shared_class_table()
+                        ->GetUnboxedFieldsMapAt(super.id());
     }
   }
   // If the super class is parameterized, use the same type_arguments field,
@@ -3752,8 +3760,8 @@
       // Sets the new size in the class table.
       isolate->class_table()->SetAt(id(), raw());
       if (FLAG_precompiled_mode) {
-        isolate->group()->class_table()->SetUnboxedFieldsMapAt(id(),
-                                                               host_bitmap);
+        isolate->group()->shared_class_table()->SetUnboxedFieldsMapAt(
+            id(), host_bitmap);
       }
     }
   }
@@ -3848,7 +3856,7 @@
 
 bool Class::TraceAllocation(Isolate* isolate) const {
 #ifndef PRODUCT
-  auto class_table = isolate->group()->class_table();
+  auto class_table = isolate->group()->shared_class_table();
   return class_table->TraceAllocationFor(id());
 #else
   return false;
@@ -3860,7 +3868,7 @@
   Isolate* isolate = Isolate::Current();
   const bool changed = trace_allocation != this->TraceAllocation(isolate);
   if (changed) {
-    auto class_table = isolate->group()->class_table();
+    auto class_table = isolate->group()->shared_class_table();
     class_table->SetTraceAllocationFor(id(), trace_allocation);
     DisableAllocationStub();
   }
@@ -7469,6 +7477,7 @@
   }
 }
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
 bool Function::CanBeInlined() const {
   // Our force-optimized functions cannot deoptimize to an unoptimized frame.
   // If the instructions of the force-optimized function body get moved via
@@ -7479,14 +7488,17 @@
   if (ForceOptimize()) {
     return CompilerState::Current().is_aot();
   }
-#if defined(PRODUCT)
-  return is_inlinable() && !is_external() && !is_generated_body();
-#else
+
+#if !defined(PRODUCT)
   Thread* thread = Thread::Current();
-  return is_inlinable() && !is_external() && !is_generated_body() &&
-         !thread->isolate()->debugger()->HasBreakpoint(*this, thread->zone());
-#endif
+  if (thread->isolate()->debugger()->HasBreakpoint(*this, thread->zone())) {
+    return false;
+  }
+#endif  // !defined(PRODUCT)
+
+  return is_inlinable() && !is_external() && !is_generated_body();
 }
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
 intptr_t Function::NumParameters() const {
   return num_fixed_parameters() + NumOptionalParameters();
@@ -17545,7 +17557,7 @@
   Instance& member = Instance::Handle();
 
   const auto unboxed_fields_bitmap =
-      thread->isolate()->group()->class_table()->GetUnboxedFieldsMapAt(
+      thread->isolate()->group()->shared_class_table()->GetUnboxedFieldsMapAt(
           GetClassId());
 
   for (intptr_t offset = Instance::NextFieldOffset(); offset < instance_size;
@@ -17597,7 +17609,7 @@
     const intptr_t instance_size = SizeFromClass();
     ASSERT(instance_size != 0);
     const auto unboxed_fields_bitmap =
-        thread->isolate()->group()->class_table()->GetUnboxedFieldsMapAt(
+        thread->isolate()->group()->shared_class_table()->GetUnboxedFieldsMapAt(
             GetClassId());
     for (intptr_t offset = Instance::NextFieldOffset(); offset < instance_size;
          offset += kWordSize) {
@@ -23529,25 +23541,66 @@
   return result.raw();
 }
 
+#if defined(DART_PRECOMPILER) || defined(DART_PRECOMPILED_RUNTIME)
+static void PrintStackTraceFrameBodyFromDSO(ZoneTextBuffer* buffer,
+                                            uword call_addr,
+                                            bool print_virtual_address) {
+  uword dso_base;
+  char* dso_name;
+  if (NativeSymbolResolver::LookupSharedObject(call_addr, &dso_base,
+                                               &dso_name)) {
+    uword dso_offset = call_addr - dso_base;
+    if (print_virtual_address) {
+      buffer->Printf(" virt %" Pp "", dso_offset);
+    }
+    uword symbol_start;
+    if (auto const symbol_name =
+            NativeSymbolResolver::LookupSymbolName(call_addr, &symbol_start)) {
+      uword symbol_offset = call_addr - symbol_start;
+      buffer->Printf(" %s+0x%" Px "", symbol_name, symbol_offset);
+      NativeSymbolResolver::FreeSymbolName(symbol_name);
+    } else {
+      buffer->Printf(" %s", dso_name);
+    }
+    NativeSymbolResolver::FreeSymbolName(dso_name);
+  } else {
+    buffer->Printf(" <unknown>");
+  }
+  buffer->Printf("\n");
+}
+#endif
+
+static void PrintStackTraceFrameIndex(ZoneTextBuffer* buffer,
+                                      intptr_t frame_index) {
+  buffer->Printf("#%-6" Pd "", frame_index);
+}
+
+static void PrintStackTraceFrameBody(ZoneTextBuffer* buffer,
+                                     const char* function_name,
+                                     const char* url,
+                                     intptr_t line = -1,
+                                     intptr_t column = -1) {
+  buffer->Printf(" %s (%s", function_name, url);
+  if (line >= 0) {
+    buffer->Printf(":%" Pd "", line);
+    if (column >= 0) {
+      buffer->Printf(":%" Pd "", column);
+    }
+  }
+  buffer->Printf(")\n");
+}
+
 static void PrintStackTraceFrame(Zone* zone,
                                  ZoneTextBuffer* buffer,
                                  const Function& function,
                                  TokenPosition token_pos,
                                  intptr_t frame_index) {
-  auto& script = Script::Handle(zone);
-  const char* function_name;
-  const char* url;
-
-  if (!function.IsNull()) {
-    script = function.script();
-    auto& handle = String::Handle(zone, function.QualifiedUserVisibleName());
-    function_name = handle.ToCString();
-    handle = script.IsNull() ? String::New("Kernel") : script.url();
-    url = handle.ToCString();
-  } else {
-    function_name = Symbols::OptimizedOut().ToCString();
-    url = function_name;
-  }
+  ASSERT(!function.IsNull());
+  const auto& script = Script::Handle(zone, function.script());
+  auto& handle = String::Handle(zone, function.QualifiedUserVisibleName());
+  auto const function_name = handle.ToCString();
+  handle = script.IsNull() ? String::New("Kernel") : script.url();
+  auto url = handle.ToCString();
 
   // If the URI starts with "data:application/dart;" this is a URI encoded
   // script so we shouldn't print the entire URI because it could be very long.
@@ -23559,30 +23612,13 @@
   intptr_t column = -1;
   if (FLAG_precompiled_mode) {
     line = token_pos.value();
-  } else if (!script.IsNull() && token_pos.IsSourcePosition()) {
+  } else if (token_pos.IsSourcePosition()) {
+    ASSERT(!script.IsNull());
     script.GetTokenLocation(token_pos.SourcePosition(), &line, &column);
   }
 
-  buffer->Printf("#%-6" Pd " %s (%s", frame_index, function_name, url);
-  if (line >= 0) {
-    buffer->Printf(":%" Pd "", line);
-    if (column >= 0) {
-      buffer->Printf(":%" Pd "", column);
-    }
-  }
-  buffer->Printf(")\n");
-}
-
-static inline bool ShouldPrintFrame(const Function& function) {
-  // TODO(dartbug.com/41052): Currently, we print frames where the function
-  // object was optimized out in the precompiled runtime, even if the original
-  // function was not visible. We may want to either elide such frames, or
-  // instead store additional information in the WSR that allows us to determine
-  // the original visibility.
-#if defined(DART_PRECOMPILED_RUNTIME)
-  if (function.IsNull()) return true;
-#endif
-  return FLAG_show_invisible_frames || function.is_visible();
+  PrintStackTraceFrameIndex(buffer, frame_index);
+  PrintStackTraceFrameBody(buffer, function_name, url, line, column);
 }
 
 const char* StackTrace::ToDartCString(const StackTrace& stack_trace_in) {
@@ -23621,32 +23657,39 @@
         if (code_object.IsCode()) {
           code ^= code_object.raw();
           ASSERT(code.IsFunctionCode());
-          if (code.is_optimized() && stack_trace.expand_inlined()) {
+          function = code.function();
+          const uword pc = code.PayloadStart() + pc_offset;
+          if (function.IsNull()) {
+#if defined(DART_PRECOMPILED_RUNTIME)
+            PrintStackTraceFrameIndex(&buffer, frame_index);
+            PrintStackTraceFrameBodyFromDSO(&buffer, pc - 1,
+                                            /*print_virtual_address=*/false);
+            frame_index++;
+#else
+            UNREACHABLE();
+#endif
+          } else if (code.is_optimized() && stack_trace.expand_inlined()) {
             code.GetInlinedFunctionsAtReturnAddress(
                 pc_offset, &inlined_functions, &inlined_token_positions);
             ASSERT(inlined_functions.length() >= 1);
             for (intptr_t j = inlined_functions.length() - 1; j >= 0; j--) {
               const auto& inlined = *inlined_functions[j];
               auto const pos = inlined_token_positions[j];
-              if (ShouldPrintFrame(inlined)) {
+              if (FLAG_show_invisible_frames || function.is_visible()) {
                 PrintStackTraceFrame(zone, &buffer, inlined, pos, frame_index);
                 frame_index++;
               }
             }
-          } else {
-            function = code.function();
-            if (ShouldPrintFrame(function)) {
-              uword pc = code.PayloadStart() + pc_offset;
-              auto const pos = code.GetTokenIndexOfPC(pc);
-              PrintStackTraceFrame(zone, &buffer, function, pos, frame_index);
-              frame_index++;
-            }
+          } else if (FLAG_show_invisible_frames || function.is_visible()) {
+            auto const pos = code.GetTokenIndexOfPC(pc);
+            PrintStackTraceFrame(zone, &buffer, function, pos, frame_index);
+            frame_index++;
           }
         } else {
           ASSERT(code_object.IsBytecode());
           bytecode ^= code_object.raw();
           function = bytecode.function();
-          if (ShouldPrintFrame(function)) {
+          if (FLAG_show_invisible_frames || function.is_visible()) {
             uword pc = bytecode.PayloadStart() + pc_offset;
             auto const pos = bytecode.GetTokenIndexOfPC(pc);
             PrintStackTraceFrame(zone, &buffer, function, pos, frame_index);
@@ -23724,26 +23767,8 @@
         uword return_addr = start + pc_offset;
         uword call_addr = return_addr - 1;
         buffer.Printf("    #%02" Pd " abs %" Pp "", frame_index, call_addr);
-        uword dso_base;
-        char* dso_name;
-        if (NativeSymbolResolver::LookupSharedObject(call_addr, &dso_base,
-                                                     &dso_name)) {
-          uword dso_offset = call_addr - dso_base;
-          buffer.Printf(" virt %" Pp "", dso_offset);
-          uword symbol_start;
-          if (auto const symbol_name = NativeSymbolResolver::LookupSymbolName(
-                  call_addr, &symbol_start)) {
-            uword symbol_offset = call_addr - symbol_start;
-            buffer.Printf(" %s+0x%" Px "", symbol_name, symbol_offset);
-            NativeSymbolResolver::FreeSymbolName(symbol_name);
-          } else {
-            buffer.Printf(" %s", dso_name);
-          }
-          NativeSymbolResolver::FreeSymbolName(dso_name);
-        } else {
-          buffer.Printf(" <unknown>");
-        }
-        buffer.Printf("\n");
+        PrintStackTraceFrameBodyFromDSO(&buffer, call_addr,
+                                        /*print_virtual_address=*/true);
         frame_index++;
       }
     }
@@ -23761,9 +23786,27 @@
 #endif  // defined(DART_PRECOMPILER) || defined(DART_PRECOMPILED_RUNTIME)
 }
 
+static void DwarfStackTracesHandler(bool value) {
+  FLAG_dwarf_stack_traces_mode = value;
+
+#if defined(PRODUCT)
+  // We can safely remove function objects in precompiled snapshots if the
+  // runtime will generate DWARF stack traces and we don't have runtime
+  // debugging options like the observatory available.
+  if (value) {
+    FLAG_retain_function_objects = false;
+  }
+#endif
+}
+
+DEFINE_FLAG_HANDLER(DwarfStackTracesHandler,
+                    dwarf_stack_traces,
+                    "Emit DWARF line number and inlining info in dylib "
+                    "snapshots and don't symbolize stack traces.");
+
 const char* StackTrace::ToCString() const {
 #if defined(DART_PRECOMPILER) || defined(DART_PRECOMPILED_RUNTIME)
-  if (FLAG_dwarf_stack_traces) {
+  if (FLAG_dwarf_stack_traces_mode) {
     return ToDwarfCString(*this);
   }
 #endif
diff --git a/runtime/vm/object_reload.cc b/runtime/vm/object_reload.cc
index b3c70c1..9bbb011 100644
--- a/runtime/vm/object_reload.cc
+++ b/runtime/vm/object_reload.cc
@@ -848,7 +848,8 @@
   // Make sure the declaration types argument count matches for the two classes.
   // ex. class A<int,B> {} cannot be replace with class A<B> {}.
   auto group_context = context->group_reload_context();
-  auto shared_class_table = group_context->isolate_group()->class_table();
+  auto shared_class_table =
+      group_context->isolate_group()->shared_class_table();
   if (NumTypeArguments() != replacement.NumTypeArguments()) {
     group_context->AddReasonForCancelling(
         new (context->zone())
diff --git a/runtime/vm/object_store.cc b/runtime/vm/object_store.cc
index 1b6f035..595a724 100644
--- a/runtime/vm/object_store.cc
+++ b/runtime/vm/object_store.cc
@@ -16,6 +16,91 @@
 
 namespace dart {
 
+IsolateObjectStore::IsolateObjectStore(ObjectStore* object_store)
+    : object_store_(object_store) {}
+
+IsolateObjectStore::~IsolateObjectStore() {}
+
+void IsolateObjectStore::VisitObjectPointers(ObjectPointerVisitor* visitor) {
+  ASSERT(visitor != NULL);
+  visitor->set_gc_root_type("isolate_object store");
+  visitor->VisitPointers(from(), to());
+  visitor->clear_gc_root_type();
+}
+
+void IsolateObjectStore::Init() {
+#define INIT_FIELD(Type, name) name##_ = Type::null();
+  ISOLATE_OBJECT_STORE_FIELD_LIST(INIT_FIELD, INIT_FIELD)
+#undef INIT_FIELD
+
+  for (RawObject** current = from(); current <= to(); current++) {
+    ASSERT(*current == Object::null());
+  }
+}
+
+#ifndef PRODUCT
+void IsolateObjectStore::PrintToJSONObject(JSONObject* jsobj) {
+  jsobj->AddProperty("type", "_IsolateObjectStore");
+
+  {
+    JSONObject fields(jsobj, "fields");
+    Object& value = Object::Handle();
+#define PRINT_OBJECT_STORE_FIELD(type, name)                                   \
+  value = name##_;                                                             \
+  fields.AddProperty(#name "_", value);
+    ISOLATE_OBJECT_STORE_FIELD_LIST(PRINT_OBJECT_STORE_FIELD,
+                                    PRINT_OBJECT_STORE_FIELD);
+#undef PRINT_OBJECT_STORE_FIELD
+  }
+}
+#endif  // !PRODUCT
+
+static RawUnhandledException* CreatePreallocatedUnandledException(
+    Zone* zone,
+    const Object& out_of_memory) {
+  // Allocate pre-allocated unhandled exception object initialized with the
+  // pre-allocated OutOfMemoryError.
+  const UnhandledException& unhandled_exception =
+      UnhandledException::Handle(UnhandledException::New(
+          Instance::Cast(out_of_memory), StackTrace::Handle(zone)));
+  return unhandled_exception.raw();
+}
+
+static RawStackTrace* CreatePreallocatedStackTrace(Zone* zone) {
+  const Array& code_array = Array::Handle(
+      zone, Array::New(StackTrace::kPreallocatedStackdepth, Heap::kOld));
+  const Array& pc_offset_array = Array::Handle(
+      zone, Array::New(StackTrace::kPreallocatedStackdepth, Heap::kOld));
+  const StackTrace& stack_trace =
+      StackTrace::Handle(zone, StackTrace::New(code_array, pc_offset_array));
+  // Expansion of inlined functions requires additional memory at run time,
+  // avoid it.
+  stack_trace.set_expand_inlined(false);
+  return stack_trace.raw();
+}
+
+RawError* IsolateObjectStore::PreallocateObjects() {
+  Thread* thread = Thread::Current();
+  Isolate* isolate = thread->isolate();
+  Zone* zone = thread->zone();
+  ASSERT(isolate != NULL && isolate->isolate_object_store() == this);
+  ASSERT(preallocated_stack_trace() == StackTrace::null());
+  resume_capabilities_ = GrowableObjectArray::New();
+  exit_listeners_ = GrowableObjectArray::New();
+  error_listeners_ = GrowableObjectArray::New();
+
+  // Allocate pre-allocated unhandled exception object initialized with the
+  // pre-allocated OutOfMemoryError.
+  const Object& out_of_memory =
+      Object::Handle(zone, object_store_->out_of_memory());
+  set_preallocated_unhandled_exception(UnhandledException::Handle(
+      CreatePreallocatedUnandledException(zone, out_of_memory)));
+  set_preallocated_stack_trace(
+      StackTrace::Handle(CreatePreallocatedStackTrace(zone)));
+
+  return Error::null();
+}
+
 ObjectStore::ObjectStore() {
 #define INIT_FIELD(Type, name) name##_ = Type::null();
   OBJECT_STORE_FIELD_LIST(INIT_FIELD, INIT_FIELD)
@@ -35,16 +120,10 @@
   visitor->clear_gc_root_type();
 }
 
-void ObjectStore::Init(Isolate* isolate) {
-  ASSERT(isolate->object_store() == NULL);
-  ObjectStore* store = new ObjectStore();
-  isolate->set_object_store(store);
-
-  if (!Dart::VmIsolateNameEquals(isolate->name())) {
-#define DO(member, name) store->set_##member(StubCode::name());
-    OBJECT_STORE_STUB_CODE_LIST(DO)
+void ObjectStore::InitStubs() {
+#define DO(member, name) set_##member(StubCode::name());
+  OBJECT_STORE_STUB_CODE_LIST(DO)
 #undef DO
-  }
 }
 
 #ifndef PRODUCT
@@ -72,22 +151,22 @@
 
 RawError* ObjectStore::PreallocateObjects() {
   Thread* thread = Thread::Current();
+  IsolateGroup* isolate_group = thread->isolate_group();
   Isolate* isolate = thread->isolate();
-  Zone* zone = thread->zone();
-  ASSERT(isolate != NULL && isolate->object_store() == this);
+  // Either we are the object store on isolate group, or isolate group has no
+  // object store and we are the object store on the isolate.
+  ASSERT(isolate_group != NULL && (isolate_group->object_store() == this ||
+                                   (isolate_group->object_store() == nullptr &&
+                                    isolate->object_store() == this)));
+
   if (this->stack_overflow() != Instance::null()) {
     ASSERT(this->out_of_memory() != Instance::null());
-    ASSERT(this->preallocated_stack_trace() != StackTrace::null());
     return Error::null();
   }
   ASSERT(this->stack_overflow() == Instance::null());
   ASSERT(this->out_of_memory() == Instance::null());
-  ASSERT(this->preallocated_stack_trace() == StackTrace::null());
 
   this->closure_functions_ = GrowableObjectArray::New();
-  this->resume_capabilities_ = GrowableObjectArray::New();
-  this->exit_listeners_ = GrowableObjectArray::New();
-  this->error_listeners_ = GrowableObjectArray::New();
 
   Object& result = Object::Handle();
   const Library& library = Library::Handle(Library::CoreLibrary());
@@ -104,24 +183,6 @@
   }
   set_out_of_memory(Instance::Cast(result));
 
-  // Allocate pre-allocated unhandled exception object initialized with the
-  // pre-allocated OutOfMemoryError.
-  const UnhandledException& unhandled_exception =
-      UnhandledException::Handle(UnhandledException::New(
-          Instance::Cast(result), StackTrace::Handle(zone)));
-  set_preallocated_unhandled_exception(unhandled_exception);
-
-  const Array& code_array = Array::Handle(
-      zone, Array::New(StackTrace::kPreallocatedStackdepth, Heap::kOld));
-  const Array& pc_offset_array = Array::Handle(
-      zone, Array::New(StackTrace::kPreallocatedStackdepth, Heap::kOld));
-  const StackTrace& stack_trace =
-      StackTrace::Handle(zone, StackTrace::New(code_array, pc_offset_array));
-  // Expansion of inlined functions requires additional memory at run time,
-  // avoid it.
-  stack_trace.set_expand_inlined(false);
-  set_preallocated_stack_trace(stack_trace);
-
   return Error::null();
 }
 
@@ -272,10 +333,4 @@
 #endif
 }
 
-void ObjectStore::PostLoad() {
-  resume_capabilities_ = GrowableObjectArray::New();
-  exit_listeners_ = GrowableObjectArray::New();
-  error_listeners_ = GrowableObjectArray::New();
-}
-
 }  // namespace dart
diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h
index 5a8640c..ecffe42 100644
--- a/runtime/vm/object_store.h
+++ b/runtime/vm/object_store.h
@@ -34,6 +34,9 @@
 // TODO(liama): Once NNBD is enabled, *_type will be deleted and all uses will
 // be replaced with *_type_non_nullable. Later, once we drop support for opted
 // out code, *_type_legacy will be deleted.
+//
+// R_ - needs getter only
+// RW - needs getter and setter
 #define OBJECT_STORE_FIELD_LIST(R_, RW)                                        \
   RW(Class, object_class)                                                      \
   RW(Type, object_type)                                                        \
@@ -149,8 +152,6 @@
   RW(GrowableObjectArray, pending_classes)                                     \
   RW(Instance, stack_overflow)                                                 \
   RW(Instance, out_of_memory)                                                  \
-  RW(UnhandledException, preallocated_unhandled_exception)                     \
-  RW(StackTrace, preallocated_stack_trace)                                     \
   RW(Function, lookup_port_handler)                                            \
   RW(Function, handle_message_function)                                        \
   RW(Function, growable_list_factory)                                          \
@@ -163,7 +164,6 @@
   RW(Function, complete_on_async_return)                                       \
   RW(Class, async_star_stream_controller)                                      \
   RW(Array, bytecode_attributes)                                               \
-  RW(Array, saved_unlinked_calls)                                              \
   RW(GrowableObjectArray, llvm_constant_pool)                                  \
   RW(GrowableObjectArray, llvm_function_pool)                                  \
   RW(Array, llvm_constant_hash_table)                                          \
@@ -198,12 +198,10 @@
   RW(Code, call_closure_no_such_method_stub)                                   \
   R_(Code, megamorphic_call_miss_code)                                         \
   R_(Function, megamorphic_call_miss_function)                                 \
-  R_(GrowableObjectArray, resume_capabilities)                                 \
-  R_(GrowableObjectArray, exit_listeners)                                      \
-  R_(GrowableObjectArray, error_listeners)                                     \
   RW(Array, dispatch_table_code_entries)                                       \
   RW(Array, code_order_table)                                                  \
   RW(Array, obfuscation_map)                                                   \
+  RW(Array, saved_initial_field_values)                                        \
   RW(Class, ffi_pointer_class)                                                 \
   RW(Class, ffi_native_type_class)                                             \
   RW(Class, ffi_struct_class)                                                  \
@@ -238,8 +236,80 @@
   DO(init_static_field_stub, InitStaticField)                                  \
   DO(instance_of_stub, InstanceOf)
 
-// The object store is a per isolate instance which stores references to
-// objects used by the VM.
+#define ISOLATE_OBJECT_STORE_FIELD_LIST(R_, RW)                                \
+  RW(UnhandledException, preallocated_unhandled_exception)                     \
+  RW(StackTrace, preallocated_stack_trace)                                     \
+  R_(GrowableObjectArray, resume_capabilities)                                 \
+  R_(GrowableObjectArray, exit_listeners)                                      \
+  R_(GrowableObjectArray, error_listeners)
+// Please remember the last entry must be referred in the 'to' function below.
+
+class IsolateObjectStore {
+ public:
+  explicit IsolateObjectStore(ObjectStore* object_store);
+  ~IsolateObjectStore();
+
+#define DECLARE_GETTER(Type, name)                                             \
+  Raw##Type* name() const { return name##_; }                                  \
+  static intptr_t name##_offset() {                                            \
+    return OFFSET_OF(IsolateObjectStore, name##_);                             \
+  }
+
+#define DECLARE_GETTER_AND_SETTER(Type, name)                                  \
+  DECLARE_GETTER(Type, name)                                                   \
+  void set_##name(const Type& value) { name##_ = value.raw(); }
+  ISOLATE_OBJECT_STORE_FIELD_LIST(DECLARE_GETTER, DECLARE_GETTER_AND_SETTER)
+#undef DECLARE_GETTER
+#undef DECLARE_GETTER_AND_SETTER
+
+  // Visit all object pointers.
+  void VisitObjectPointers(ObjectPointerVisitor* visitor);
+
+  // Called to initialize objects required by the vm but which invoke
+  // dart code.  If an error occurs the error object is returned otherwise
+  // a null object is returned.
+  RawError* PreallocateObjects();
+
+  void Init();
+  void PostLoad();
+
+  ObjectStore* object_store() const { return object_store_; }
+  void set_object_store(ObjectStore* object_store) {
+    ASSERT(object_store_ == nullptr);
+    object_store_ = object_store;
+  }
+
+  static intptr_t object_store_offset() {
+    return OFFSET_OF(IsolateObjectStore, object_store_);
+  }
+
+#ifndef PRODUCT
+  void PrintToJSONObject(JSONObject* jsobj);
+#endif
+
+ private:
+  // Finds a core library private method in Object.
+  RawFunction* PrivateObjectLookup(const String& name);
+
+  RawObject** from() {
+    return reinterpret_cast<RawObject**>(&preallocated_unhandled_exception_);
+  }
+#define DECLARE_OBJECT_STORE_FIELD(type, name) Raw##type* name##_;
+  ISOLATE_OBJECT_STORE_FIELD_LIST(DECLARE_OBJECT_STORE_FIELD,
+                                  DECLARE_OBJECT_STORE_FIELD)
+#undef DECLARE_OBJECT_STORE_FIELD
+  RawObject** to() { return reinterpret_cast<RawObject**>(&error_listeners_); }
+
+  ObjectStore* object_store_;
+
+  friend class Serializer;
+  friend class Deserializer;
+
+  DISALLOW_COPY_AND_ASSIGN(IsolateObjectStore);
+};
+
+// The object store is a per isolate group instance which stores references to
+// objects used by the VM shared by all isolates in a group.
 class ObjectStore {
  public:
   enum BootstrapLibraryId {
@@ -249,6 +319,7 @@
 #undef MAKE_ID
   };
 
+  ObjectStore();
   ~ObjectStore();
 
 #define DECLARE_GETTER(Type, name)                                             \
@@ -306,17 +377,13 @@
 
   void InitKnownObjects();
 
-  void PostLoad();
-
-  static void Init(Isolate* isolate);
+  void InitStubs();
 
 #ifndef PRODUCT
   void PrintToJSONObject(JSONObject* jsobj);
 #endif
 
  private:
-  ObjectStore();
-
   // Finds a core library private method in Object.
   RawFunction* PrivateObjectLookup(const String& name);
 
diff --git a/runtime/vm/program_visitor.cc b/runtime/vm/program_visitor.cc
index 93c239a..b6f364f 100644
--- a/runtime/vm/program_visitor.cc
+++ b/runtime/vm/program_visitor.cc
@@ -13,101 +13,199 @@
 
 namespace dart {
 
-class ProgramWalker : public CodeVisitor {
+class WorklistElement : public ZoneAllocated {
  public:
-  ProgramWalker(Zone* zone, ClassVisitor* visitor)
-      : visitor_(visitor),
-        object_(Object::Handle(zone)),
-        fields_(Array::Handle(zone)),
-        field_(Field::Handle(zone)),
-        functions_(Array::Handle(zone)),
-        function_(Function::Handle(zone)),
-        code_(Code::Handle(zone)) {}
+  WorklistElement(Zone* zone, const Object& object)
+      : object_(Object::Handle(zone, object.raw())), next_(nullptr) {}
 
+  RawObject* value() const { return object_.raw(); }
+
+  void set_next(WorklistElement* elem) { next_ = elem; }
+  WorklistElement* next() const { return next_; }
+
+ private:
+  const Object& object_;
+  WorklistElement* next_;
+
+  DISALLOW_COPY_AND_ASSIGN(WorklistElement);
+};
+
+// Implements a FIFO queue, using IsEmpty, Add, Remove operations.
+class Worklist : public ValueObject {
+ public:
+  explicit Worklist(Zone* zone)
+      : zone_(zone), first_(nullptr), last_(nullptr) {}
+
+  bool IsEmpty() const { return first_ == nullptr; }
+
+  void Add(const Object& value) {
+    auto element = new (zone_) WorklistElement(zone_, value);
+    if (first_ == nullptr) {
+      first_ = element;
+      ASSERT(last_ == nullptr);
+    } else {
+      ASSERT(last_ != nullptr);
+      last_->set_next(element);
+    }
+    last_ = element;
+    ASSERT(first_ != nullptr && last_ != nullptr);
+  }
+
+  RawObject* Remove() {
+    ASSERT(first_ != nullptr);
+    WorklistElement* result = first_;
+    first_ = first_->next();
+    if (first_ == nullptr) {
+      last_ = nullptr;
+    }
+    return result->value();
+  }
+
+ private:
+  Zone* const zone_;
+  WorklistElement* first_;
+  WorklistElement* last_;
+
+  DISALLOW_COPY_AND_ASSIGN(Worklist);
+};
+
+// Walks through the classes, functions, and code for the current program.
+//
+// Uses the heap object ID table to determine whether or not a given object
+// has been visited already.
+class ProgramWalker : public ValueObject {
+ public:
+  ProgramWalker(Zone* zone, Heap* heap, ClassVisitor* visitor)
+      : heap_(heap),
+        visitor_(visitor),
+        worklist_(zone),
+        class_object_(Object::Handle(zone)),
+        class_fields_(Array::Handle(zone)),
+        class_field_(Field::Handle(zone)),
+        class_functions_(Array::Handle(zone)),
+        class_function_(Function::Handle(zone)),
+        class_code_(Code::Handle(zone)),
+        function_code_(Code::Handle(zone)),
+        static_calls_array_(Array::Handle(zone)),
+        static_call_code_(Code::Handle(zone)),
+        worklist_entry_(Object::Handle(zone)) {}
+
+  ~ProgramWalker() { heap_->ResetObjectIdTable(); }
+
+  // Adds the given object to the worklist if it's an object type that the
+  // visitor can visit.
+  void AddToWorklist(const Object& object) {
+    // We don't visit null, non-heap objects, or objects in the VM heap.
+    if (object.IsNull() || object.IsSmi() || object.InVMIsolateHeap()) return;
+    // Check and set visited, even if we don't end up adding this to the list.
+    if (heap_->GetObjectId(object.raw()) != 0) return;
+    heap_->SetObjectId(object.raw(), 1);
+    if (object.IsClass() ||
+        (object.IsFunction() && visitor_->IsFunctionVisitor()) ||
+        (object.IsCode() && visitor_->IsCodeVisitor())) {
+      worklist_.Add(object);
+    }
+  }
+
+  void VisitWorklist() {
+    while (!worklist_.IsEmpty()) {
+      worklist_entry_ = worklist_.Remove();
+      if (worklist_entry_.IsClass()) {
+        VisitClass(Class::Cast(worklist_entry_));
+      } else if (worklist_entry_.IsFunction()) {
+        VisitFunction(Function::Cast(worklist_entry_));
+      } else if (worklist_entry_.IsCode()) {
+        VisitCode(Code::Cast(worklist_entry_));
+      } else {
+        FATAL1("Got unexpected object %s", worklist_entry_.ToCString());
+      }
+    }
+  }
+
+ private:
   void VisitClass(const Class& cls) {
-    ASSERT(!cls.IsNull());
-    if (cls.InVMIsolateHeap()) return;
     visitor_->VisitClass(cls);
 
     if (!visitor_->IsFunctionVisitor()) return;
 
-    functions_ = cls.functions();
-    for (intptr_t j = 0; j < functions_.Length(); j++) {
-      function_ ^= functions_.At(j);
-      VisitFunction(function_);
-      if (function_.HasImplicitClosureFunction()) {
-        function_ = function_.ImplicitClosureFunction();
-        VisitFunction(function_);
+    class_functions_ = cls.functions();
+    for (intptr_t j = 0; j < class_functions_.Length(); j++) {
+      class_function_ ^= class_functions_.At(j);
+      AddToWorklist(class_function_);
+      if (class_function_.HasImplicitClosureFunction()) {
+        class_function_ = class_function_.ImplicitClosureFunction();
+        AddToWorklist(class_function_);
       }
     }
 
-    functions_ = cls.invocation_dispatcher_cache();
-    for (intptr_t j = 0; j < functions_.Length(); j++) {
-      object_ = functions_.At(j);
-      if (object_.IsFunction()) {
-        function_ ^= functions_.At(j);
-        VisitFunction(function_);
+    class_functions_ = cls.invocation_dispatcher_cache();
+    for (intptr_t j = 0; j < class_functions_.Length(); j++) {
+      class_object_ = class_functions_.At(j);
+      if (class_object_.IsFunction()) {
+        class_function_ ^= class_functions_.At(j);
+        AddToWorklist(class_function_);
       }
     }
 
-    fields_ = cls.fields();
-    for (intptr_t j = 0; j < fields_.Length(); j++) {
-      field_ ^= fields_.At(j);
-      if (field_.is_static() && field_.HasInitializerFunction()) {
-        function_ = field_.InitializerFunction();
-        VisitFunction(function_);
+    class_fields_ = cls.fields();
+    for (intptr_t j = 0; j < class_fields_.Length(); j++) {
+      class_field_ ^= class_fields_.At(j);
+      if (class_field_.is_static() && class_field_.HasInitializerFunction()) {
+        class_function_ = class_field_.InitializerFunction();
+        AddToWorklist(class_function_);
       }
     }
 
     if (!visitor_->IsCodeVisitor()) return;
 
-    code_ = cls.allocation_stub();
-    if (!code_.IsNull()) VisitCode(code_);
+    class_code_ = cls.allocation_stub();
+    if (!class_code_.IsNull()) AddToWorklist(class_code_);
   }
 
   void VisitFunction(const Function& function) {
     ASSERT(visitor_->IsFunctionVisitor());
-    ASSERT(!function.IsNull());
-    if (function.InVMIsolateHeap()) return;
     visitor_->AsFunctionVisitor()->VisitFunction(function);
     if (!visitor_->IsCodeVisitor() || !function.HasCode()) return;
-    code_ = function.CurrentCode();
-    VisitCode(code_);
+    function_code_ = function.CurrentCode();
+    AddToWorklist(function_code_);
   }
 
   void VisitCode(const Code& code) {
     ASSERT(visitor_->IsCodeVisitor());
-    ASSERT(!code.IsNull());
-    if (code.InVMIsolateHeap()) return;
     visitor_->AsCodeVisitor()->VisitCode(code);
-  }
 
-  void VisitObject(const Object& object) {
-    if (object.IsNull()) return;
-    if (object.IsClass()) {
-      VisitClass(Class::Cast(object));
-    } else if (visitor_->IsFunctionVisitor() && object.IsFunction()) {
-      VisitFunction(Function::Cast(object));
-    } else if (visitor_->IsCodeVisitor() && object.IsCode()) {
-      VisitCode(Code::Cast(object));
+    // If the precompiler can drop function objects not needed at runtime,
+    // then some entries in the static calls table may need to be visited.
+    static_calls_array_ = code.static_calls_target_table();
+    if (static_calls_array_.IsNull()) return;
+    StaticCallsTable static_calls(static_calls_array_);
+    for (auto& view : static_calls) {
+      static_call_code_ = view.Get<Code::kSCallTableCodeTarget>();
+      AddToWorklist(static_call_code_);
     }
   }
 
- private:
+  Heap* const heap_;
   ClassVisitor* const visitor_;
-  Object& object_;
-  Array& fields_;
-  Field& field_;
-  Array& functions_;
-  Function& function_;
-  Code& code_;
+  Worklist worklist_;
+  Object& class_object_;
+  Array& class_fields_;
+  Field& class_field_;
+  Array& class_functions_;
+  Function& class_function_;
+  Code& class_code_;
+  Code& function_code_;
+  Array& static_calls_array_;
+  Code& static_call_code_;
+  Object& worklist_entry_;
 };
 
 void ProgramVisitor::WalkProgram(Zone* zone,
                                  Isolate* isolate,
                                  ClassVisitor* visitor) {
   auto const object_store = isolate->object_store();
-  ProgramWalker walker(zone, visitor);
+  auto const heap = isolate->heap();
+  ProgramWalker walker(zone, heap, visitor);
 
   // Walk through the libraries and patches, looking for visitable objects.
   const auto& libraries =
@@ -122,29 +220,16 @@
     ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
     while (it.HasNext()) {
       cls = it.GetNextClass();
-      walker.VisitClass(cls);
+      walker.AddToWorklist(cls);
     }
     patches = lib.used_scripts();
     for (intptr_t j = 0; j < patches.Length(); j++) {
       entry = patches.At(j);
-      walker.VisitObject(entry);
+      walker.AddToWorklist(entry);
     }
   }
 
-  if (!visitor->IsFunctionVisitor()) return;
-
-  // Function objects not necessarily reachable from classes.
-  auto& function = Function::Handle(zone);
-  const auto& closures =
-      GrowableObjectArray::Handle(zone, object_store->closure_functions());
-  ASSERT(!closures.IsNull());
-  for (intptr_t i = 0; i < closures.Length(); i++) {
-    function ^= closures.At(i);
-    walker.VisitFunction(function);
-    ASSERT(!function.HasImplicitClosureFunction());
-  }
-
-  // If there's a global object pool, check for functions like FfiTrampolines.
+  // If there's a global object pool, add any visitable objects.
   const auto& global_object_pool =
       ObjectPool::Handle(zone, object_store->global_object_pool());
   if (!global_object_pool.IsNull()) {
@@ -153,25 +238,38 @@
       auto const type = global_object_pool.TypeAt(i);
       if (type != ObjectPool::EntryType::kTaggedObject) continue;
       object = global_object_pool.ObjectAt(i);
-      if (object.IsFunction() && visitor->IsFunctionVisitor()) {
-        walker.VisitFunction(Function::Cast(object));
+      walker.AddToWorklist(object);
+    }
+  }
+
+  if (visitor->IsFunctionVisitor()) {
+    // Function objects not necessarily reachable from classes.
+    auto& function = Function::Handle(zone);
+    const auto& closures =
+        GrowableObjectArray::Handle(zone, object_store->closure_functions());
+    ASSERT(!closures.IsNull());
+    for (intptr_t i = 0; i < closures.Length(); i++) {
+      function ^= closures.At(i);
+      walker.AddToWorklist(function);
+      ASSERT(!function.HasImplicitClosureFunction());
+    }
+  }
+
+  if (visitor->IsCodeVisitor()) {
+    // Code objects not necessarily reachable from functions.
+    auto& code = Code::Handle(zone);
+    const auto& dispatch_table_entries =
+        Array::Handle(zone, object_store->dispatch_table_code_entries());
+    if (!dispatch_table_entries.IsNull()) {
+      for (intptr_t i = 0; i < dispatch_table_entries.Length(); i++) {
+        code ^= dispatch_table_entries.At(i);
+        walker.AddToWorklist(code);
       }
     }
   }
 
-  if (!visitor->IsCodeVisitor()) return;
-
-  // Code objects not necessarily reachable from functions.
-  auto& code = Code::Handle(zone);
-  const auto& dispatch_table_entries =
-      Array::Handle(zone, object_store->dispatch_table_code_entries());
-  if (!dispatch_table_entries.IsNull()) {
-    for (intptr_t i = 0; i < dispatch_table_entries.Length(); i++) {
-      code ^= dispatch_table_entries.At(i);
-      if (code.IsNull()) continue;
-      walker.VisitCode(code);
-    }
-  }
+  // Walk the program starting from any roots we added to the worklist.
+  walker.VisitWorklist();
 }
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
@@ -710,6 +808,7 @@
     Smi& reason_and_flags_;
   };
 
+  if (FLAG_precompiled_mode) return;
   DedupDeoptEntriesVisitor visitor(zone);
   WalkProgram(zone, isolate, &visitor);
 }
@@ -1072,9 +1171,13 @@
     void VisitCode(const Code& code) {
       instructions_ = code.instructions();
       instructions_ = Dedup(instructions_);
+      code.set_instructions(instructions_);
+      if (code.IsDisabled()) {
+        instructions_ = code.active_instructions();
+        instructions_ = Dedup(instructions_);
+      }
       code.SetActiveInstructions(instructions_,
                                  code.UncheckedEntryPointOffset());
-      code.set_instructions(instructions_);
       if (!code.IsFunctionCode()) return;
       function_ = code.function();
       if (function_.IsNull()) return;
@@ -1147,7 +1250,7 @@
   ShareMegamorphicBuckets(zone, isolate);
   NormalizeAndDedupCompressedStackMaps(zone, isolate);
   DedupPcDescriptors(zone, isolate);
-  NOT_IN_PRECOMPILED(DedupDeoptEntries(zone, isolate));
+  DedupDeoptEntries(zone, isolate);
 #if defined(DART_PRECOMPILER)
   DedupCatchEntryMovesMaps(zone, isolate);
   DedupUnlinkedCalls(zone, isolate);
diff --git a/runtime/vm/program_visitor.h b/runtime/vm/program_visitor.h
index 9edc6ad..e10ffd2 100644
--- a/runtime/vm/program_visitor.h
+++ b/runtime/vm/program_visitor.h
@@ -28,11 +28,11 @@
 // VisitFunction.
 //
 // There are no guarantees for the order in which objects of a given type will
-// be visited or how many times they will be visited. The only guarantee is that
-// objects will be visited before any visitable sub-objects they contain. For
-// example, if a FunctionVisitor has a VisitClass implementation that drops
-// methods from a class, the function objects for those methods will not be
-// visited unless they are also found via another source of function objects.
+// be visited, but each object will be visited only once. In addition, each
+// object is visited before any visitable sub-objects it contains. For example,
+// this means a FunctionVisitor with a VisitClass implementation that drops
+// methods from a class will not visit the dropped methods unless they are also
+// found via another source of function objects.
 //
 // Note that WalkProgram only visits objects in the isolate heap. Deduplicating
 // visitors that want to use VM objects as canonical when possible should
diff --git a/runtime/vm/raw_object.cc b/runtime/vm/raw_object.cc
index 85c05f2..871aa0b1 100644
--- a/runtime/vm/raw_object.cc
+++ b/runtime/vm/raw_object.cc
@@ -64,11 +64,11 @@
     }
   }
   const intptr_t class_id = ClassIdTag::decode(tags);
-  if (!isolate_group->class_table()->IsValidIndex(class_id)) {
+  if (!isolate_group->shared_class_table()->IsValidIndex(class_id)) {
     FATAL1("Invalid class id encountered %" Pd "\n", class_id);
   }
   if (class_id == kNullCid &&
-      isolate_group->class_table()->HasValidClassAt(class_id)) {
+      isolate_group->shared_class_table()->HasValidClassAt(class_id)) {
     // Null class not yet initialized; skip.
     return;
   }
@@ -241,7 +241,7 @@
       const bool use_saved_class_table = false;
 #endif
 
-      auto class_table = isolate_group->class_table();
+      auto class_table = isolate_group->shared_class_table();
       ASSERT(use_saved_class_table || class_table->SizeAt(class_id) > 0);
       if (!class_table->IsValidIndex(class_id) ||
           (!class_table->HasValidClassAt(class_id) && !use_saved_class_table)) {
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index 07293c9..b3758a2 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -2665,6 +2665,7 @@
 
   friend class LinkedHashMapSerializationCluster;
   friend class LinkedHashMapDeserializationCluster;
+  friend class CodeSerializationCluster;
   friend class CodeDeserializationCluster;
   friend class Deserializer;
   friend class RawCode;
diff --git a/runtime/vm/regexp.cc b/runtime/vm/regexp.cc
index 95567a9..08384be 100644
--- a/runtime/vm/regexp.cc
+++ b/runtime/vm/regexp.cc
@@ -14,12 +14,15 @@
 #include "vm/dart_entry.h"
 #include "vm/regexp_assembler.h"
 #include "vm/regexp_assembler_bytecode.h"
-#include "vm/regexp_assembler_ir.h"
 #include "vm/regexp_ast.h"
 #include "vm/symbols.h"
 #include "vm/thread.h"
 #include "vm/unibrow-inl.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/regexp_assembler_ir.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 #define Z (zone())
 
 namespace dart {
@@ -701,7 +704,7 @@
 
   // Omit flushing the trace. We discard the entire stack frame anyway.
 
-  if (!label()->IsBound()) {
+  if (!label()->is_bound()) {
     // We are completely independent of the trace, since we ignore it,
     // so this code can be used as the generic version.
     assembler->BindBlock(label());
@@ -728,7 +731,7 @@
     return;
   }
   RegExpMacroAssembler* assembler = compiler->macro_assembler();
-  if (!label()->IsBound()) {
+  if (!label()->is_bound()) {
     assembler->BindBlock(label());
   }
   switch (action_) {
@@ -1126,7 +1129,7 @@
   EmitDoubleBoundaryTest(masm, ranges->At(cut_index),
                          ranges->At(cut_index + 1) - 1, &dummy, in_range_label,
                          &dummy);
-  ASSERT(!dummy.IsLinked());
+  ASSERT(!dummy.is_linked());
   // Cut out the single range by rewriting the array.  This creates a new
   // range that is a merger of the two ranges on either side of the one we
   // are cutting out.  The oddity of the labels is preserved.
@@ -1314,7 +1317,7 @@
   GenerateBranches(masm, ranges, start_index, new_end_index, min_char,
                    border - 1, &dummy, even_label, odd_label);
 
-  if (handle_rest.IsLinked()) {
+  if (handle_rest.is_linked()) {
     masm->BindBlock(&handle_rest);
     bool flip = (new_start_index & 1) != (start_index & 1);
     GenerateBranches(masm, ranges, new_start_index, end_index, border, max_char,
@@ -1432,7 +1435,7 @@
 
   RegExpMacroAssembler* macro_assembler = compiler->macro_assembler();
   if (trace->is_trivial()) {
-    if (label_.IsBound()) {
+    if (label_.is_bound()) {
       // We are being asked to generate a generic version, but that's already
       // been done so just go to it.
       macro_assembler->GoTo(&label_);
@@ -3352,7 +3355,7 @@
                                            AlternativeGeneration* alt_gen,
                                            intptr_t preload_characters,
                                            bool next_expects_preload) {
-  if (!alt_gen->possible_success.IsLinked()) return;
+  if (!alt_gen->possible_success.is_linked()) return;
 
   RegExpMacroAssembler* macro_assembler = compiler->macro_assembler();
   macro_assembler->BindBlock(&alt_gen->possible_success);
@@ -3621,7 +3624,7 @@
   printer.PrintBit("WI", info->follows_word_interest);
   printer.PrintBit("SI", info->follows_start_interest);
   BlockLabel* label = that->label();
-  if (label->IsBound()) printer.PrintPositive("@", label->Position());
+  if (label->is_bound()) printer.PrintPositive("@", label->pos());
   OS::PrintErr(
       "}\"];\n"
       "  a%p -> n%p [style=dashed, color=grey, arrowhead=none];\n",
diff --git a/runtime/vm/regexp.h b/runtime/vm/regexp.h
index 170d412..7cefc38 100644
--- a/runtime/vm/regexp.h
+++ b/runtime/vm/regexp.h
@@ -7,9 +7,6 @@
 
 #include "platform/unicode.h"
 
-#include "vm/compiler/assembler/assembler.h"
-#include "vm/compiler/backend/flow_graph_compiler.h"
-#include "vm/compiler/backend/il.h"
 #include "vm/object.h"
 #include "vm/regexp_assembler.h"
 #include "vm/splay-tree.h"
diff --git a/runtime/vm/regexp_assembler.cc b/runtime/vm/regexp_assembler.cc
index ab242c4..8fa69ac 100644
--- a/runtime/vm/regexp_assembler.cc
+++ b/runtime/vm/regexp_assembler.cc
@@ -10,6 +10,7 @@
 
 #include "vm/flags.h"
 #include "vm/regexp.h"
+#include "vm/runtime_entry.h"
 #include "vm/unibrow-inl.h"
 
 namespace dart {
@@ -96,8 +97,7 @@
     false /* is_float */,
     reinterpret_cast<RuntimeFunction>(&CaseInsensitiveCompareUTF16));
 
-BlockLabel::BlockLabel()
-    : block_(NULL), is_bound_(false), is_linked_(false), pos_(-1) {
+BlockLabel::BlockLabel() {
 #if !defined(DART_PRECOMPILED_RUNTIME)
   if (!FLAG_interpret_irregexp) {
     // Only needed by the compiled IR backend.
diff --git a/runtime/vm/regexp_assembler.h b/runtime/vm/regexp_assembler.h
index 818f573..d7a460b 100644
--- a/runtime/vm/regexp_assembler.h
+++ b/runtime/vm/regexp_assembler.h
@@ -5,9 +5,12 @@
 #ifndef RUNTIME_VM_REGEXP_ASSEMBLER_H_
 #define RUNTIME_VM_REGEXP_ASSEMBLER_H_
 
+#include "vm/object.h"
+
+#if !defined(DART_PRECOMPILED_RUNTIME)
 #include "vm/compiler/assembler/assembler.h"
 #include "vm/compiler/backend/il.h"
-#include "vm/object.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
 namespace dart {
 
@@ -32,60 +35,59 @@
   // Used by the IR assembler.
  public:
   BlockLabel();
-
-  JoinEntryInstr* block() const { return block_; }
-
-  bool IsBound() const { return is_bound_; }
-  void SetBound(intptr_t block_id) {
-    ASSERT(!is_bound_);
-    block_->set_block_id(block_id);
-    is_bound_ = true;
-  }
-
-  bool IsLinked() const { return !is_bound_ && is_linked_; }
-  void SetLinked() { is_linked_ = true; }
-
-  intptr_t Position() const {
-    ASSERT(IsBound());
-    return block_->block_id();
-  }
-
- private:
-  JoinEntryInstr* block_;
-
-  bool is_bound_;
-  bool is_linked_;
-
-  // Used by the bytecode assembler.
- public:
   ~BlockLabel() { ASSERT(!is_linked()); }
 
   intptr_t pos() const { return pos_; }
-  bool is_bound() const { return IsBound(); }
-  bool is_linked() const { return IsLinked(); }
+  bool is_bound() const { return is_bound_; }
+  bool is_linked() const { return !is_bound_ && is_linked_; }
+#if !defined(DART_PRECOMPILED_RUNTIME)
+  JoinEntryInstr* block() const { return block_; }
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
   void Unuse() {
-    pos_ = 0;
+    pos_ = -1;
     is_bound_ = false;
     is_linked_ = false;
   }
 
-  void bind_to(intptr_t pos) {
+  void BindTo(intptr_t pos) {
     pos_ = pos;
+#if !defined(DART_PRECOMPILED_RUNTIME)
+    if (block_ != nullptr) block_->set_block_id(pos);
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
     is_bound_ = true;
     is_linked_ = false;
     ASSERT(is_bound());
   }
 
-  void link_to(intptr_t pos) {
+  // Used by bytecode assembler to form a linked list out of
+  // forward jumps to an unbound label.
+  void LinkTo(intptr_t pos) {
+#if !defined(DART_PRECOMPILED_RUNTIME)
+    ASSERT(block_ == nullptr);
+#endif
+    ASSERT(!is_bound_);
     pos_ = pos;
-    is_bound_ = false;
     is_linked_ = true;
-    ASSERT(is_linked());
+  }
+
+  // Used by IR builder to mark block label as used.
+  void SetLinked() {
+#if !defined(DART_PRECOMPILED_RUNTIME)
+    ASSERT(block_ != nullptr);
+#endif
+    if (!is_bound_) {
+      is_linked_ = true;
+    }
   }
 
  private:
-  intptr_t pos_;
+  bool is_bound_ = false;
+  bool is_linked_ = false;
+  intptr_t pos_ = -1;
+#if !defined(DART_PRECOMPILED_RUNTIME)
+  JoinEntryInstr* block_ = nullptr;
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 };
 
 class RegExpMacroAssembler : public ZoneAllocated {
diff --git a/runtime/vm/regexp_assembler_bytecode.cc b/runtime/vm/regexp_assembler_bytecode.cc
index c075a86..4118969 100644
--- a/runtime/vm/regexp_assembler_bytecode.cc
+++ b/runtime/vm/regexp_assembler_bytecode.cc
@@ -44,7 +44,7 @@
       *reinterpret_cast<uint32_t*>(buffer_->data() + fixup) = pc_;
     }
   }
-  l->bind_to(pc_);
+  l->BindTo(pc_);
 }
 
 void BytecodeRegExpMacroAssembler::EmitOrLink(BlockLabel* l) {
@@ -56,7 +56,7 @@
     if (l->is_linked()) {
       pos = l->pos();
     }
-    l->link_to(pc_);
+    l->LinkTo(pc_);
     Emit32(pos);
   }
 }
diff --git a/runtime/vm/regexp_assembler_ir.cc b/runtime/vm/regexp_assembler_ir.cc
index 500a52f..2c295ef 100644
--- a/runtime/vm/regexp_assembler_ir.cc
+++ b/runtime/vm/regexp_assembler_ir.cc
@@ -682,10 +682,10 @@
 // If the BlockLabel does not yet contain a block, it is created.
 // If there is a current instruction, append a goto to the bound block.
 void IRRegExpMacroAssembler::BindBlock(BlockLabel* label) {
-  ASSERT(!label->IsBound());
+  ASSERT(!label->is_bound());
   ASSERT(label->block()->next() == NULL);
 
-  label->SetBound(block_id_.Alloc());
+  label->BindTo(block_id_.Alloc());
   blocks_.Add(label->block());
 
   if (current_instruction_ != NULL) {
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 6bb8000..32bab52 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -4,14 +4,14 @@
 
 #include "vm/runtime_entry.h"
 
+#include "vm/code_descriptors.h"
 #include "vm/code_patcher.h"
-#include "vm/compiler/assembler/assembler.h"
-#include "vm/compiler/frontend/bytecode_reader.h"
+#include "vm/compiler/api/deopt_id.h"
+#include "vm/compiler/api/type_check_mode.h"
 #include "vm/compiler/jit/compiler.h"
 #include "vm/dart_api_impl.h"
 #include "vm/dart_entry.h"
 #include "vm/debugger.h"
-#include "vm/deopt_instructions.h"
 #include "vm/exceptions.h"
 #include "vm/flags.h"
 #include "vm/heap/verifier.h"
@@ -30,6 +30,10 @@
 #include "vm/thread_registry.h"
 #include "vm/type_testing_stubs.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/deopt_instructions.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 DEFINE_FLAG(
@@ -342,14 +346,12 @@
     const intptr_t length =
         Array::LengthOf(reinterpret_cast<RawArray*>(object));
     add_to_remembered_set =
-        CreateArrayInstr::WillAllocateNewOrRemembered(length);
+        compiler::target::WillAllocateNewOrRememberedArray(length);
   } else if (object->IsContext()) {
     const intptr_t num_context_variables =
         Context::NumVariables(reinterpret_cast<RawContext*>(object));
     add_to_remembered_set =
-        AllocateContextInstr::WillAllocateNewOrRemembered(
-            num_context_variables) ||
-        AllocateUninitializedContextInstr::WillAllocateNewOrRemembered(
+        compiler::target::WillAllocateNewOrRememberedContext(
             num_context_variables);
   }
 
@@ -416,10 +418,14 @@
   // Code inlined in the caller should have optimized the case where the
   // instantiator can be reused as type argument vector.
   ASSERT(!type_arguments.IsUninstantiatedIdentity());
-  type_arguments = type_arguments.InstantiateAndCanonicalizeFrom(
-      instantiator_type_arguments, function_type_arguments);
-  ASSERT(type_arguments.IsNull() || type_arguments.IsInstantiated());
-  arguments.SetReturn(type_arguments);
+  thread->isolate_group()->RunWithStoppedMutators(
+      [&]() {
+        type_arguments = type_arguments.InstantiateAndCanonicalizeFrom(
+            instantiator_type_arguments, function_type_arguments);
+        ASSERT(type_arguments.IsNull() || type_arguments.IsInstantiated());
+        arguments.SetReturn(type_arguments);
+      },
+      /*use_force_growth=*/true);
 }
 
 // Instantiate type.
@@ -596,6 +602,7 @@
 // This operation is currently very slow (lookup of code is not efficient yet).
 static void UpdateTypeTestCache(
     Zone* zone,
+    Thread* thread,
     const Instance& instance,
     const AbstractType& type,
     const TypeArguments& instantiator_type_arguments,
@@ -639,94 +646,103 @@
       instance_type_arguments = instance.GetTypeArguments();
     }
   }
-  const intptr_t len = new_cache.NumberOfChecks();
-  if (len >= FLAG_max_subtype_cache_entries) {
-    if (FLAG_trace_type_checks) {
-      OS::PrintErr("Not updating subtype test cache as its length reached %d\n",
-                   FLAG_max_subtype_cache_entries);
-    }
-    return;
-  }
+  thread->isolate_group()->RunWithStoppedMutators(
+      [&]() {
+        const intptr_t len = new_cache.NumberOfChecks();
+        if (len >= FLAG_max_subtype_cache_entries) {
+          if (FLAG_trace_type_checks) {
+            OS::PrintErr(
+                "Not updating subtype test cache as its length reached %d\n",
+                FLAG_max_subtype_cache_entries);
+          }
+          return;
+        }
 #if defined(DEBUG)
-  ASSERT(instance_type_arguments.IsNull() ||
-         instance_type_arguments.IsCanonical());
-  ASSERT(instantiator_type_arguments.IsNull() ||
-         instantiator_type_arguments.IsCanonical());
-  ASSERT(function_type_arguments.IsNull() ||
-         function_type_arguments.IsCanonical());
-  ASSERT(instance_parent_function_type_arguments.IsNull() ||
-         instance_parent_function_type_arguments.IsCanonical());
-  ASSERT(instance_delayed_type_arguments.IsNull() ||
-         instance_delayed_type_arguments.IsCanonical());
-  auto& last_instance_class_id_or_function = Object::Handle(zone);
-  auto& last_instance_type_arguments = TypeArguments::Handle(zone);
-  auto& last_instantiator_type_arguments = TypeArguments::Handle(zone);
-  auto& last_function_type_arguments = TypeArguments::Handle(zone);
-  auto& last_instance_parent_function_type_arguments =
-      TypeArguments::Handle(zone);
-  auto& last_instance_delayed_type_arguments = TypeArguments::Handle(zone);
-  Bool& last_result = Bool::Handle(zone);
-  for (intptr_t i = 0; i < len; ++i) {
-    new_cache.GetCheck(
-        i, &last_instance_class_id_or_function, &last_instance_type_arguments,
-        &last_instantiator_type_arguments, &last_function_type_arguments,
-        &last_instance_parent_function_type_arguments,
-        &last_instance_delayed_type_arguments, &last_result);
-    if ((last_instance_class_id_or_function.raw() ==
-         instance_class_id_or_function.raw()) &&
-        (last_instance_type_arguments.raw() == instance_type_arguments.raw()) &&
-        (last_instantiator_type_arguments.raw() ==
-         instantiator_type_arguments.raw()) &&
-        (last_function_type_arguments.raw() == function_type_arguments.raw()) &&
-        (last_instance_parent_function_type_arguments.raw() ==
-         instance_parent_function_type_arguments.raw()) &&
-        (last_instance_delayed_type_arguments.raw() ==
-         instance_delayed_type_arguments.raw())) {
-      OS::PrintErr("  Error in test cache %p ix: %" Pd ",", new_cache.raw(), i);
-      PrintTypeCheck(" duplicate cache entry", instance, type,
-                     instantiator_type_arguments, function_type_arguments,
-                     result);
-      UNREACHABLE();
-      return;
-    }
-  }
+        ASSERT(instance_type_arguments.IsNull() ||
+               instance_type_arguments.IsCanonical());
+        ASSERT(instantiator_type_arguments.IsNull() ||
+               instantiator_type_arguments.IsCanonical());
+        ASSERT(function_type_arguments.IsNull() ||
+               function_type_arguments.IsCanonical());
+        ASSERT(instance_parent_function_type_arguments.IsNull() ||
+               instance_parent_function_type_arguments.IsCanonical());
+        ASSERT(instance_delayed_type_arguments.IsNull() ||
+               instance_delayed_type_arguments.IsCanonical());
+        auto& last_instance_class_id_or_function = Object::Handle(zone);
+        auto& last_instance_type_arguments = TypeArguments::Handle(zone);
+        auto& last_instantiator_type_arguments = TypeArguments::Handle(zone);
+        auto& last_function_type_arguments = TypeArguments::Handle(zone);
+        auto& last_instance_parent_function_type_arguments =
+            TypeArguments::Handle(zone);
+        auto& last_instance_delayed_type_arguments =
+            TypeArguments::Handle(zone);
+        Bool& last_result = Bool::Handle(zone);
+        for (intptr_t i = 0; i < len; ++i) {
+          new_cache.GetCheck(
+              i, &last_instance_class_id_or_function,
+              &last_instance_type_arguments, &last_instantiator_type_arguments,
+              &last_function_type_arguments,
+              &last_instance_parent_function_type_arguments,
+              &last_instance_delayed_type_arguments, &last_result);
+          if ((last_instance_class_id_or_function.raw() ==
+               instance_class_id_or_function.raw()) &&
+              (last_instance_type_arguments.raw() ==
+               instance_type_arguments.raw()) &&
+              (last_instantiator_type_arguments.raw() ==
+               instantiator_type_arguments.raw()) &&
+              (last_function_type_arguments.raw() ==
+               function_type_arguments.raw()) &&
+              (last_instance_parent_function_type_arguments.raw() ==
+               instance_parent_function_type_arguments.raw()) &&
+              (last_instance_delayed_type_arguments.raw() ==
+               instance_delayed_type_arguments.raw())) {
+            // Some other isolate might have updated the cache between entry was
+            // found missing and now.
+            return;
+          }
+        }
 #endif
-  new_cache.AddCheck(instance_class_id_or_function, instance_type_arguments,
-                     instantiator_type_arguments, function_type_arguments,
-                     instance_parent_function_type_arguments,
-                     instance_delayed_type_arguments, result);
-  if (FLAG_trace_type_checks) {
-    AbstractType& test_type = AbstractType::Handle(zone, type.raw());
-    if (!test_type.IsInstantiated()) {
-      test_type = type.InstantiateFrom(instantiator_type_arguments,
-                                       function_type_arguments, kAllFree, NULL,
-                                       Heap::kNew);
-    }
-    const auto& type_class = Class::Handle(zone, test_type.type_class());
-    const auto& instance_class_name =
-        String::Handle(zone, instance_class.Name());
-    OS::PrintErr(
-        "  Updated test cache %p ix: %" Pd
-        " with "
-        "(cid-or-fun: %p, type-args: %p, i-type-args: %p, f-type-args: %p, "
-        "p-type-args: %p, d-type-args: %p, result: %s)\n"
-        "    instance  [class: (%p '%s' cid: %" Pd
-        "),    type-args: %p %s]\n"
-        "    test-type [class: (%p '%s' cid: %" Pd
-        "), i-type-args: %p %s, f-type-args: %p %s]\n",
-        new_cache.raw(), len, instance_class_id_or_function.raw(),
-        instance_type_arguments.raw(), instantiator_type_arguments.raw(),
-        function_type_arguments.raw(),
-        instance_parent_function_type_arguments.raw(),
-        instance_delayed_type_arguments.raw(), result.ToCString(),
-        instance_class.raw(), instance_class_name.ToCString(),
-        instance_class.id(), instance_type_arguments.raw(),
-        instance_type_arguments.ToCString(), type_class.raw(),
-        String::Handle(zone, type_class.Name()).ToCString(), type_class.id(),
-        instantiator_type_arguments.raw(),
-        instantiator_type_arguments.ToCString(), function_type_arguments.raw(),
-        function_type_arguments.ToCString());
-  }
+        new_cache.AddCheck(instance_class_id_or_function,
+                           instance_type_arguments, instantiator_type_arguments,
+                           function_type_arguments,
+                           instance_parent_function_type_arguments,
+                           instance_delayed_type_arguments, result);
+        if (FLAG_trace_type_checks) {
+          AbstractType& test_type = AbstractType::Handle(zone, type.raw());
+          if (!test_type.IsInstantiated()) {
+            test_type = type.InstantiateFrom(instantiator_type_arguments,
+                                             function_type_arguments, kAllFree,
+                                             NULL, Heap::kNew);
+          }
+          const auto& type_class = Class::Handle(zone, test_type.type_class());
+          const auto& instance_class_name =
+              String::Handle(zone, instance_class.Name());
+          OS::PrintErr(
+              "  Updated test cache %p ix: %" Pd
+              " with "
+              "(cid-or-fun: %p, type-args: %p, i-type-args: %p, f-type-args: "
+              "%p, "
+              "p-type-args: %p, d-type-args: %p, result: %s)\n"
+              "    instance  [class: (%p '%s' cid: %" Pd
+              "),    type-args: %p %s]\n"
+              "    test-type [class: (%p '%s' cid: %" Pd
+              "), i-type-args: %p %s, f-type-args: %p %s]\n",
+              new_cache.raw(), len, instance_class_id_or_function.raw(),
+              instance_type_arguments.raw(), instantiator_type_arguments.raw(),
+              function_type_arguments.raw(),
+              instance_parent_function_type_arguments.raw(),
+              instance_delayed_type_arguments.raw(), result.ToCString(),
+              instance_class.raw(), instance_class_name.ToCString(),
+              instance_class.id(), instance_type_arguments.raw(),
+              instance_type_arguments.ToCString(), type_class.raw(),
+              String::Handle(zone, type_class.Name()).ToCString(),
+              type_class.id(), instantiator_type_arguments.raw(),
+              instantiator_type_arguments.ToCString(),
+              function_type_arguments.raw(),
+              function_type_arguments.ToCString());
+        }
+      },
+      /*use_force_growth=*/true);
 }
 
 // Check that the given instance is an instance of the given type.
@@ -756,7 +772,7 @@
     PrintTypeCheck("InstanceOf", instance, type, instantiator_type_arguments,
                    function_type_arguments, result);
   }
-  UpdateTypeTestCache(zone, instance, type, instantiator_type_arguments,
+  UpdateTypeTestCache(zone, thread, instance, type, instantiator_type_arguments,
                       function_type_arguments, result, cache);
   arguments.SetReturn(result);
 }
@@ -920,18 +936,26 @@
       TypeTestingStubCallPattern tts_pattern(caller_frame->pc());
       const intptr_t stc_pool_idx = tts_pattern.GetSubtypeTestCachePoolIndex();
 
-      // The pool entry must be initialized to `null` when we patch it.
-      ASSERT(pool.ObjectAt(stc_pool_idx) == Object::null());
-      cache = SubtypeTestCache::New();
-      pool.SetObjectAt(stc_pool_idx, cache);
+      thread->isolate_group()->RunWithStoppedMutators(
+          [&]() {
+            // If nobody has updated the pool since the check, we are
+            // updating it now.
+            if (pool.ObjectAt(stc_pool_idx) == Object::null()) {
+              cache = SubtypeTestCache::New();
+              pool.SetObjectAt(stc_pool_idx, cache);
+            }
+          },
+          /*use_force_growth=*/true);
 #else
       UNREACHABLE();
 #endif
     }
 
-    UpdateTypeTestCache(zone, src_instance, dst_type,
-                        instantiator_type_arguments, function_type_arguments,
-                        Bool::True(), cache);
+    if (!cache.IsNull()) {  //  we might have lost the race to set up new cache
+      UpdateTypeTestCache(zone, thread, src_instance, dst_type,
+                          instantiator_type_arguments, function_type_arguments,
+                          Bool::True(), cache);
+    }
   }
 
   arguments.SetReturn(src_instance);
@@ -1458,36 +1482,45 @@
                              Isolate* isolate,
                              uword frame_pc,
                              const UnlinkedCall& unlinked_call) {
-  auto object_store = isolate->object_store();
-  if (object_store->saved_unlinked_calls() == Array::null()) {
+  IsolateGroup* isolate_group = isolate->group();
+  if (isolate_group->saved_unlinked_calls() == Array::null()) {
     const auto& initial_map =
         Array::Handle(zone, HashTables::New<UnlinkedCallMap>(16, Heap::kOld));
-    object_store->set_saved_unlinked_calls(initial_map);
+    isolate_group->set_saved_unlinked_calls(initial_map);
   }
 
-  UnlinkedCallMap unlinked_call_map(zone, object_store->saved_unlinked_calls());
+  UnlinkedCallMap unlinked_call_map(zone,
+                                    isolate_group->saved_unlinked_calls());
   const auto& pc = Integer::Handle(Integer::NewFromUint64(frame_pc));
-  const bool was_present = unlinked_call_map.UpdateOrInsert(pc, unlinked_call);
-  // We transition at most once out of UnlinkedCall state.
-  RELEASE_ASSERT(!was_present);
-  object_store->set_saved_unlinked_calls(unlinked_call_map.Release());
+  // Some other isolate might have updated unlinked_call_map[pc] too, but
+  // their update should be identical to ours.
+  UnlinkedCall& new_or_old_value = UnlinkedCall::Handle(
+      zone, UnlinkedCall::RawCast(
+                unlinked_call_map.InsertOrGetValue(pc, unlinked_call)));
+  RELEASE_ASSERT(new_or_old_value.raw() == unlinked_call.raw());
+  isolate_group->set_saved_unlinked_calls(unlinked_call_map.Release());
 }
 
 #if defined(DART_PRECOMPILED_RUNTIME)
 static RawUnlinkedCall* LoadUnlinkedCall(Zone* zone,
                                          Isolate* isolate,
-                                         uword pc) {
-  auto object_store = isolate->object_store();
-  ASSERT(object_store->saved_unlinked_calls() != Array::null());
+                                         uword pc,
+                                         bool is_monomorphic_hit) {
+  IsolateGroup* isolate_group = isolate->group();
+  ASSERT(isolate_group->saved_unlinked_calls() != Array::null());
 
-  UnlinkedCallMap unlinked_call_map(zone, object_store->saved_unlinked_calls());
+  UnlinkedCallMap unlinked_call_map(zone,
+                                    isolate_group->saved_unlinked_calls());
+
   const auto& pc_integer = Integer::Handle(Integer::NewFromUint64(pc));
   const auto& unlinked_call = UnlinkedCall::Cast(
       Object::Handle(zone, unlinked_call_map.GetOrDie(pc_integer)));
-  // Since we transition out of the monomorphic state only once, we should not
-  // need the saved unlinked call anymore.
-  unlinked_call_map.Remove(pc_integer);
-  object_store->set_saved_unlinked_calls(unlinked_call_map.Release());
+  // Only remove entry from unlinked_call_map if we are actually transitioning
+  // out of monomorphic state.
+  if (!is_monomorphic_hit) {
+    unlinked_call_map.Remove(pc_integer);
+    isolate_group->set_saved_unlinked_calls(unlinked_call_map.Release());
+  }
 
   return unlinked_call.raw();
 }
@@ -1624,8 +1657,8 @@
       code = StubCode::MonomorphicSmiableCheck().raw();
     }
   }
-  CodePatcher::PatchSwitchableCallAt(caller_frame_->pc(), caller_code_, object,
-                                     code);
+  CodePatcher::PatchSwitchableCallAtWithMutatorsStopped(
+      thread_, caller_frame_->pc(), caller_code_, object, code);
 
   // Return the ICData. The miss stub will jump to continue in the IC lookup
   // stub.
@@ -1692,11 +1725,16 @@
     UNREACHABLE();
   }
 
+  // The site might have just been updated to monomorphic state with same
+  // exact class id, in which case we are staying in monomorphic state.
+  bool is_monomorphic_hit = old_expected_cid == receiver_.GetClassId();
+
   String& name = String::Handle(zone_);
   Array& descriptor = Array::Handle(zone_);
   if (FLAG_use_bare_instructions && FLAG_dedup_instructions) {
     const UnlinkedCall& unlinked_call = UnlinkedCall::Handle(
-        zone_, LoadUnlinkedCall(zone_, isolate_, caller_frame_->pc()));
+        zone_, LoadUnlinkedCall(zone_, isolate_, caller_frame_->pc(),
+                                is_monomorphic_hit));
     name = unlinked_call.target_name();
     descriptor = unlinked_call.args_descriptor();
 
@@ -1741,6 +1779,14 @@
     ic_data.AddReceiverCheck(old_expected_cid, old_target);
   }
 
+  if (is_monomorphic_hit) {
+    // The site just have been updated to monomorphic state with same
+    // exact class id - do nothing in that case: stub will call through ic data.
+    arguments_.SetArgAt(0, StubCode::ICCallThroughCode());
+    arguments_.SetReturn(ic_data);
+    return;
+  }
+
   const Function& target_function = Function::Handle(
       zone_, ResolveAndAddReceiverCheck(name, descriptor, ic_data));
 
@@ -1756,8 +1802,8 @@
     cache.set_lower_limit(lower);
     cache.set_upper_limit(upper);
     const Code& stub = StubCode::SingleTargetCall();
-    CodePatcher::PatchSwitchableCallAt(caller_frame_->pc(), caller_code_, cache,
-                                       stub);
+    CodePatcher::PatchSwitchableCallAtWithMutatorsStopped(
+        thread_, caller_frame_->pc(), caller_code_, cache, stub);
     // Return the ICData. The miss stub will jump to continue in the IC call
     // stub.
     arguments_.SetArgAt(0, StubCode::ICCallThroughCode());
@@ -1767,8 +1813,8 @@
 
   // Patch to call through stub.
   const Code& stub = StubCode::ICCallThroughCode();
-  CodePatcher::PatchSwitchableCallAt(caller_frame_->pc(), caller_code_, ic_data,
-                                     stub);
+  CodePatcher::PatchSwitchableCallAtWithMutatorsStopped(
+      thread_, caller_frame_->pc(), caller_code_, ic_data, stub);
 
   // Return the ICData. The miss stub will jump to continue in the IC lookup
   // stub.
@@ -1784,8 +1830,8 @@
   const Code& stub = ic_data.is_tracking_exactness()
                          ? StubCode::OneArgCheckInlineCacheWithExactnessCheck()
                          : StubCode::OneArgCheckInlineCache();
-  CodePatcher::PatchInstanceCallAt(caller_frame_->pc(), caller_code_, ic_data,
-                                   stub);
+  CodePatcher::PatchInstanceCallAtWithMutatorsStopped(
+      thread_, caller_frame_->pc(), caller_code_, ic_data, stub);
   if (FLAG_trace_ic) {
     OS::PrintErr("Instance call at %" Px
                  " switching to polymorphic dispatch, %s\n",
@@ -1844,8 +1890,8 @@
 
   // Call site is not single target, switch to call using ICData.
   const Code& stub = StubCode::ICCallThroughCode();
-  CodePatcher::PatchSwitchableCallAt(caller_frame_->pc(), caller_code_, ic_data,
-                                     stub);
+  CodePatcher::PatchSwitchableCallAtWithMutatorsStopped(
+      thread_, caller_frame_->pc(), caller_code_, ic_data, stub);
 
   // Return the ICData. The single target stub will jump to continue in the
   // IC call stub.
@@ -1892,20 +1938,25 @@
         Code::Handle(zone_, target_function.EnsureHasCode());
     const Smi& expected_cid =
         Smi::Handle(zone_, Smi::New(receiver_.GetClassId()));
-    CodePatcher::PatchSwitchableCallAt(caller_frame_->pc(), caller_code_,
-                                       expected_cid, target_code);
+    CodePatcher::PatchSwitchableCallAtWithMutatorsStopped(
+        thread_, caller_frame_->pc(), caller_code_, expected_cid, target_code);
     arguments_.SetArgAt(0, target_code);
     arguments_.SetReturn(expected_cid);
   } else {
-    ic_data.AddReceiverCheck(receiver_.GetClassId(), target_function);
+    // IC entry might have been added while we waited to get into runtime.
+    GrowableArray<intptr_t> class_ids(1);
+    class_ids.Add(receiver_.GetClassId());
+    if (ic_data.FindCheck(class_ids) == -1) {
+      ic_data.AddReceiverCheck(receiver_.GetClassId(), target_function);
+    }
     if (number_of_checks > FLAG_max_polymorphic_checks) {
       // Switch to megamorphic call.
       const MegamorphicCache& cache = MegamorphicCache::Handle(
           zone_, MegamorphicCacheTable::Lookup(thread_, name, descriptor));
       const Code& stub = StubCode::MegamorphicCall();
 
-      CodePatcher::PatchSwitchableCallAt(caller_frame_->pc(), caller_code_,
-                                         cache, stub);
+      CodePatcher::PatchSwitchableCallAtWithMutatorsStopped(
+          thread_, caller_frame_->pc(), caller_code_, cache, stub);
       arguments_.SetArgAt(0, stub);
       arguments_.SetReturn(cache);
     } else {
@@ -1946,14 +1997,14 @@
 }
 
 void SwitchableCallHandler::HandleMiss(const Object& old_data,
-                                       const Code& old_target) {
+                                       const Code& old_code) {
   switch (old_data.GetClassId()) {
     case kUnlinkedCallCid:
-      ASSERT(old_target.raw() == StubCode::SwitchableCallMiss().raw());
+      ASSERT(old_code.raw() == StubCode::SwitchableCallMiss().raw());
       DoUnlinkedCall(UnlinkedCall::Cast(old_data));
       break;
     case kMonomorphicSmiableCallCid:
-      ASSERT(old_target.raw() == StubCode::MonomorphicSmiableCheck().raw());
+      ASSERT(old_code.raw() == StubCode::MonomorphicSmiableCheck().raw());
       FALL_THROUGH;
 #if defined(DART_PRECOMPILED_RUNTIME)
     case kSmiCid:
@@ -1965,15 +2016,15 @@
       DoMonomorphicMiss(old_data);
       break;
     case kSingleTargetCacheCid:
-      ASSERT(old_target.raw() == StubCode::SingleTargetCall().raw());
+      ASSERT(old_code.raw() == StubCode::SingleTargetCall().raw());
       DoSingleTargetMiss(SingleTargetCache::Cast(old_data));
       break;
     case kICDataCid:
-      ASSERT(old_target.raw() == StubCode::ICCallThroughCode().raw());
+      ASSERT(old_code.raw() == StubCode::ICCallThroughCode().raw());
       DoICDataMiss(ICData::Cast(old_data));
       break;
     case kMegamorphicCacheCid:
-      ASSERT(old_target.raw() == StubCode::MegamorphicCall().raw());
+      ASSERT(old_code.raw() == StubCode::MegamorphicCall().raw());
       DoMegamorphicMiss(MegamorphicCache::Cast(old_data));
       break;
     default:
@@ -2006,20 +2057,24 @@
 
   Object& old_data = Object::Handle(zone);
   Code& old_code = Code::Handle(zone);
+  thread->isolate_group()->RunWithStoppedMutators(
+      [&]() {
 #if defined(DART_PRECOMPILED_RUNTIME)
-  old_data =
-      CodePatcher::GetSwitchableCallDataAt(caller_frame->pc(), caller_code);
+        old_data = CodePatcher::GetSwitchableCallDataAt(caller_frame->pc(),
+                                                        caller_code);
 #if defined(DEBUG)
-  old_code ^=
-      CodePatcher::GetSwitchableCallTargetAt(caller_frame->pc(), caller_code);
+        old_code ^= CodePatcher::GetSwitchableCallTargetAt(caller_frame->pc(),
+                                                           caller_code);
 #endif
 #else
-  old_code ^= CodePatcher::GetInstanceCallAt(caller_frame->pc(), caller_code,
-                                             &old_data);
+        old_code ^= CodePatcher::GetInstanceCallAt(caller_frame->pc(),
+                                                   caller_code, &old_data);
 #endif
-  SwitchableCallHandler handler(thread, receiver, arguments, caller_frame,
-                                caller_code, caller_function);
-  handler.HandleMiss(old_data, old_code);
+        SwitchableCallHandler handler(thread, receiver, arguments, caller_frame,
+                                      caller_code, caller_function);
+        handler.HandleMiss(old_data, old_code);
+      },
+      /*use_force_growth=*/true);
 }
 
 // Handles interpreted interface call cache miss.
diff --git a/runtime/vm/runtime_entry.h b/runtime/vm/runtime_entry.h
index c29e6e4..83d8a63 100644
--- a/runtime/vm/runtime_entry.h
+++ b/runtime/vm/runtime_entry.h
@@ -11,6 +11,7 @@
 #endif
 #include "vm/flags.h"
 #include "vm/heap/safepoint.h"
+#include "vm/log.h"
 #include "vm/native_arguments.h"
 #include "vm/runtime_entry_list.h"
 
diff --git a/runtime/vm/runtime_entry_x64.cc b/runtime/vm/runtime_entry_x64.cc
index 0ff88f0..caaa2a4 100644
--- a/runtime/vm/runtime_entry_x64.cc
+++ b/runtime/vm/runtime_entry_x64.cc
@@ -7,8 +7,10 @@
 
 #include "vm/runtime_entry.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
 #include "vm/compiler/assembler/assembler.h"
 #include "vm/stub_code.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
 namespace dart {
 
diff --git a/runtime/vm/scopes.cc b/runtime/vm/scopes.cc
index f722603..99273cb 100644
--- a/runtime/vm/scopes.cc
+++ b/runtime/vm/scopes.cc
@@ -1,14 +1,15 @@
 // Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
+#if !defined(DART_PRECOMPILED_RUNTIME)
 
 #include "vm/scopes.h"
 
+#include "vm/compiler/backend/slot.h"
 #include "vm/object.h"
 #include "vm/stack_frame.h"
 #include "vm/symbols.h"
 
-#if !defined(DART_PRECOMPILED_RUNTIME)
 
 namespace dart {
 
diff --git a/runtime/vm/scopes.h b/runtime/vm/scopes.h
index d855c43..a592cd6 100644
--- a/runtime/vm/scopes.h
+++ b/runtime/vm/scopes.h
@@ -10,7 +10,6 @@
 #include "platform/assert.h"
 #include "platform/globals.h"
 #include "vm/allocation.h"
-#include "vm/compiler/backend/slot.h"
 #include "vm/growable_array.h"
 #include "vm/object.h"
 #include "vm/raw_object.h"
@@ -21,6 +20,7 @@
 
 class CompileType;
 class LocalScope;
+class Slot;
 
 // Indices of [LocalVariable]s are abstract and have little todo with the
 // actual frame layout!
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index 181322e..4abd3ec 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -4324,6 +4324,17 @@
   return true;
 }
 
+static const MethodParameter* get_isolate_object_store_params[] = {
+    RUNNABLE_ISOLATE_PARAMETER,
+    NULL,
+};
+
+static bool GetIsolateObjectStore(Thread* thread, JSONStream* js) {
+  JSONObject jsobj(js);
+  thread->isolate()->isolate_object_store()->PrintToJSONObject(&jsobj);
+  return true;
+}
+
 static const MethodParameter* get_class_list_params[] = {
     RUNNABLE_ISOLATE_PARAMETER, NULL,
 };
@@ -4820,6 +4831,8 @@
     get_instances_params },
   { "getIsolate", GetIsolate,
     get_isolate_params },
+  { "_getIsolateObjectStore", GetIsolateObjectStore,
+    get_isolate_object_store_params },
   { "getIsolateGroup", GetIsolateGroup,
     get_isolate_group_params },
   { "getMemoryUsage", GetMemoryUsage,
diff --git a/runtime/vm/service_isolate.cc b/runtime/vm/service_isolate.cc
index 7e084ab..59aaf47 100644
--- a/runtime/vm/service_isolate.cc
+++ b/runtime/vm/service_isolate.cc
@@ -133,7 +133,7 @@
   return isolate != nullptr && isolate == isolate_;
 }
 
-bool ServiceIsolate::IsServiceIsolateDescendant(const Isolate* isolate) {
+bool ServiceIsolate::IsServiceIsolateDescendant(Isolate* isolate) {
   MonitorLocker ml(monitor_);
   return isolate->origin_id() == origin_;
 }
diff --git a/runtime/vm/service_isolate.h b/runtime/vm/service_isolate.h
index a44b4b9..b8dd6b4 100644
--- a/runtime/vm/service_isolate.h
+++ b/runtime/vm/service_isolate.h
@@ -26,7 +26,7 @@
   static bool Exists();
   static bool IsRunning();
   static bool IsServiceIsolate(const Isolate* isolate);
-  static bool IsServiceIsolateDescendant(const Isolate* isolate);
+  static bool IsServiceIsolateDescendant(Isolate* isolate);
   static Dart_Port Port();
   static void WaitForServiceIsolateStartup();
 
@@ -97,9 +97,7 @@
   static bool Exists() { return false; }
   static bool IsRunning() { return false; }
   static bool IsServiceIsolate(const Isolate* isolate) { return false; }
-  static bool IsServiceIsolateDescendant(const Isolate* isolate) {
-    return false;
-  }
+  static bool IsServiceIsolateDescendant(Isolate* isolate) { return false; }
   static void Run() {}
   static bool SendIsolateStartupMessage() { return false; }
   static bool SendIsolateShutdownMessage() { return false; }
diff --git a/runtime/vm/snapshot.cc b/runtime/vm/snapshot.cc
index ad55238..d966d45 100644
--- a/runtime/vm/snapshot.cc
+++ b/runtime/vm/snapshot.cc
@@ -621,7 +621,8 @@
     intptr_t result_cid = result->GetClassId();
 
     const auto unboxed_fields =
-        isolate()->group()->class_table()->GetUnboxedFieldsMapAt(result_cid);
+        isolate()->group()->shared_class_table()->GetUnboxedFieldsMapAt(
+            result_cid);
 
     while (offset < next_field_offset) {
       if (unboxed_fields.Get(offset / kWordSize)) {
@@ -1464,7 +1465,7 @@
     WriteObjectImpl(cls, kAsInlinedObject);
 
     const auto unboxed_fields =
-        isolate()->group()->class_table()->GetUnboxedFieldsMapAt(
+        isolate()->group()->shared_class_table()->GetUnboxedFieldsMapAt(
             cls->ptr()->id_);
 
     // Write out all the fields for the object.
diff --git a/runtime/vm/stack_frame.cc b/runtime/vm/stack_frame.cc
index a795a0c..04309fe 100644
--- a/runtime/vm/stack_frame.cc
+++ b/runtime/vm/stack_frame.cc
@@ -5,9 +5,8 @@
 #include "vm/stack_frame.h"
 
 #include "platform/memory_sanitizer.h"
-#include "vm/compiler/assembler/assembler.h"
+#include "vm/code_descriptors.h"
 #include "vm/compiler/runtime_api.h"
-#include "vm/deopt_instructions.h"
 #include "vm/heap/become.h"
 #include "vm/isolate.h"
 #include "vm/object.h"
@@ -21,6 +20,10 @@
 #include "vm/stub_code.h"
 #include "vm/visitor.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/deopt_instructions.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 DECLARE_FLAG(bool, enable_interpreter);
@@ -847,6 +850,7 @@
 #endif  // defined(DART_PRECOMPILED_RUNTIME)
 }
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
 // Finds the potential offset for the current function's FP if the
 // current frame were to be deoptimized.
 intptr_t InlinedFunctionsIterator::GetDeoptFpOffset() const {
@@ -860,6 +864,7 @@
   UNREACHABLE();
   return 0;
 }
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
 #if defined(DEBUG)
 void ValidateFrames() {
diff --git a/runtime/vm/stack_frame.h b/runtime/vm/stack_frame.h
index f430e99..70fe3ed 100644
--- a/runtime/vm/stack_frame.h
+++ b/runtime/vm/stack_frame.h
@@ -431,7 +431,9 @@
     return code_.raw();
   }
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
   intptr_t GetDeoptFpOffset() const;
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
  private:
   void SetDone() { index_ = -1; }
diff --git a/runtime/vm/stack_trace.cc b/runtime/vm/stack_trace.cc
index 7643a8c..c0d55b8 100644
--- a/runtime/vm/stack_trace.cc
+++ b/runtime/vm/stack_trace.cc
@@ -4,11 +4,14 @@
 
 #include "vm/stack_trace.h"
 
-#include "vm/compiler/frontend/bytecode_reader.h"
 #include "vm/dart_api_impl.h"
 #include "vm/stack_frame.h"
 #include "vm/symbols.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/frontend/bytecode_reader.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 // Keep in sync with
diff --git a/runtime/vm/stub_code.cc b/runtime/vm/stub_code.cc
index 6ebb23a..21b71d5 100644
--- a/runtime/vm/stub_code.cc
+++ b/runtime/vm/stub_code.cc
@@ -7,8 +7,6 @@
 #include "platform/assert.h"
 #include "platform/globals.h"
 #include "vm/clustered_snapshot.h"
-#include "vm/compiler/aot/precompiler.h"
-#include "vm/compiler/assembler/assembler.h"
 #include "vm/compiler/assembler/disassembler.h"
 #include "vm/flags.h"
 #include "vm/heap/safepoint.h"
@@ -18,6 +16,11 @@
 #include "vm/virtual_memory.h"
 #include "vm/visitor.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/aot/precompiler.h"
+#include "vm/compiler/assembler/assembler.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 DEFINE_FLAG(bool, disassemble_stubs, false, "Disassemble generated stubs.");
diff --git a/runtime/vm/stub_code.h b/runtime/vm/stub_code.h
index be2ad99..cffe96a 100644
--- a/runtime/vm/stub_code.h
+++ b/runtime/vm/stub_code.h
@@ -6,12 +6,15 @@
 #define RUNTIME_VM_STUB_CODE_H_
 
 #include "vm/allocation.h"
-#include "vm/compiler/assembler/assembler.h"
 #include "vm/compiler/runtime_api.h"
-#include "vm/compiler/stub_code_compiler.h"
 #include "vm/object.h"
 #include "vm/stub_code_list.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/assembler/assembler.h"
+#include "vm/compiler/stub_code_compiler.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 // Forward declarations.
@@ -66,12 +69,14 @@
       compiler::ObjectPoolBuilder* pool);
 #endif
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
   // Generate the stub and finalize the generated code into the stub
   // code executable area.
   static RawCode* Generate(
       const char* name,
       compiler::ObjectPoolBuilder* object_pool_builder,
       void (*GenerateStub)(compiler::Assembler* assembler));
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
   static const Code& UnoptimizedStaticCallEntry(intptr_t num_args_tested);
 
diff --git a/runtime/vm/symbols.cc b/runtime/vm/symbols.cc
index 312e23b..bf4e645 100644
--- a/runtime/vm/symbols.cc
+++ b/runtime/vm/symbols.cc
@@ -581,12 +581,31 @@
     table.Release();
   }
   if (symbol.IsNull()) {
+    IsolateGroup* group = thread->isolate_group();
     Isolate* isolate = thread->isolate();
-    SafepointMutexLocker ml(isolate->symbols_mutex());
-    data = isolate->object_store()->symbol_table();
-    SymbolTable table(&key, &value, &data);
-    symbol ^= table.InsertNewOrGet(str);
-    isolate->object_store()->set_symbol_table(table.Release());
+    // in JIT object_store lives on isolate, not on isolate group.
+    ObjectStore* object_store = group->object_store() == nullptr
+                                    ? isolate->object_store()
+                                    : group->object_store();
+    // in AOT no need to worry about background compiler, only about
+    // other mutators.
+#if defined(DART_PRECOMPILED_RUNTIME)
+    group->RunWithStoppedMutators(
+        [&]() {
+#else
+    SafepointRwLock* symbols_lock = group->object_store() == nullptr
+                                        ? isolate->symbols_lock()
+                                        : group->symbols_lock();
+    SafepointWriteRwLocker sl(thread, symbols_lock);
+#endif
+          data = object_store->symbol_table();
+          SymbolTable table(&key, &value, &data);
+          symbol ^= table.InsertNewOrGet(str);
+          object_store->set_symbol_table(table.Release());
+#if defined(DART_PRECOMPILED_RUNTIME)
+        },
+        /*use_force_growth=*/true);
+#endif
   }
   ASSERT(symbol.IsSymbol());
   ASSERT(symbol.HasHash());
@@ -610,9 +629,21 @@
     table.Release();
   }
   if (symbol.IsNull()) {
+    IsolateGroup* group = thread->isolate_group();
     Isolate* isolate = thread->isolate();
-    SafepointMutexLocker ml(isolate->symbols_mutex());
-    data = isolate->object_store()->symbol_table();
+    // in JIT object_store lives on isolate, not on isolate group.
+    ObjectStore* object_store = group->object_store() == nullptr
+                                    ? isolate->object_store()
+                                    : group->object_store();
+    // in AOT no need to worry about background compiler, only about
+    // other mutators.
+#if !defined(DART_PRECOMPILED_RUNTIME)
+    SafepointRwLock* symbols_lock = group->object_store() == nullptr
+                                        ? isolate->symbols_lock()
+                                        : group->symbols_lock();
+    SafepointReadRwLocker sl(thread, symbols_lock);
+#endif
+    data = object_store->symbol_table();
     SymbolTable table(&key, &value, &data);
     symbol ^= table.GetOrNull(str);
     table.Release();
diff --git a/runtime/vm/thread.cc b/runtime/vm/thread.cc
index c69446c..420618d 100644
--- a/runtime/vm/thread.cc
+++ b/runtime/vm/thread.cc
@@ -5,7 +5,6 @@
 #include "vm/thread.h"
 
 #include "vm/dart_api_state.h"
-#include "vm/ffi_callback_trampolines.h"
 #include "vm/growable_array.h"
 #include "vm/heap/safepoint.h"
 #include "vm/isolate.h"
@@ -25,6 +24,10 @@
 #include "vm/timeline.h"
 #include "vm/zone.h"
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/ffi_callback_trampolines.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 namespace dart {
 
 #if !defined(PRODUCT)
diff --git a/runtime/vm/type_testing_stubs.cc b/runtime/vm/type_testing_stubs.cc
index 3fcd8b7..b55430a 100644
--- a/runtime/vm/type_testing_stubs.cc
+++ b/runtime/vm/type_testing_stubs.cc
@@ -4,10 +4,14 @@
 
 #include "vm/type_testing_stubs.h"
 #include "vm/compiler/assembler/disassembler.h"
+#include "vm/object_store.h"
+#include "vm/stub_code.h"
+#include "vm/timeline.h"
+
+#if !defined(DART_PRECOMPILED_RUNTIME)
 #include "vm/compiler/backend/flow_graph_compiler.h"
 #include "vm/compiler/backend/il_printer.h"
-#include "vm/object_store.h"
-#include "vm/timeline.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
 #define __ assembler->
 
@@ -580,6 +584,7 @@
 
 #else  // !defined(TARGET_ARCH_IA32)
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
 void RegisterTypeArgumentsUse(const Function& function,
                               TypeUsageInfo* type_usage_info,
                               const Class& klass,
@@ -587,6 +592,7 @@
   // We only have a [TypeUsageInfo] object available durin AOT compilation.
   UNREACHABLE();
 }
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
 #endif  // !defined(TARGET_ARCH_IA32)
 
diff --git a/runtime/vm/type_testing_stubs.h b/runtime/vm/type_testing_stubs.h
index fa9bb07..2b1279c 100644
--- a/runtime/vm/type_testing_stubs.h
+++ b/runtime/vm/type_testing_stubs.h
@@ -5,8 +5,12 @@
 #ifndef RUNTIME_VM_TYPE_TESTING_STUBS_H_
 #define RUNTIME_VM_TYPE_TESTING_STUBS_H_
 
+#include "vm/object.h"
+
+#if !defined(DART_PRECOMPILED_RUNTIME)
 #include "vm/compiler/assembler/assembler.h"
 #include "vm/compiler/backend/il.h"
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
 namespace dart {
 
@@ -369,10 +373,12 @@
   Class& klass_;
 };
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
 void RegisterTypeArgumentsUse(const Function& function,
                               TypeUsageInfo* type_usage_info,
                               const Class& klass,
                               Definition* type_arguments);
+#endif
 
 #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
 
diff --git a/runtime/vm/unit_test.cc b/runtime/vm/unit_test.cc
index f23e5ee..29c3245 100644
--- a/runtime/vm/unit_test.cc
+++ b/runtime/vm/unit_test.cc
@@ -117,11 +117,12 @@
   Isolate::FlagsInitialize(&api_flags);
   Dart_Isolate isolate = NULL;
   if (len == 0) {
-    isolate =
-        Dart_CreateIsolateGroup(name, NULL, data_buffer, instr_buffer,
-                                &api_flags, group_data, isolate_data, &err);
+    isolate = Dart_CreateIsolateGroup(
+        /*script_uri=*/name, /*name=*/name, data_buffer, instr_buffer,
+        &api_flags, group_data, isolate_data, &err);
   } else {
-    isolate = Dart_CreateIsolateGroupFromKernel(name, NULL, data_buffer, len,
+    isolate = Dart_CreateIsolateGroupFromKernel(/*script_uri=*/name,
+                                                /*name=*/name, data_buffer, len,
                                                 &api_flags, group_data,
                                                 isolate_data, &err);
   }
@@ -157,6 +158,26 @@
   RELEASE_ASSERT(!Dart_IsError(result));
 }
 
+Dart_Isolate TestCase::CreateTestIsolateInGroup(const char* name,
+                                                Dart_Isolate parent,
+                                                void* group_data,
+                                                void* isolate_data) {
+  char* error;
+#if defined(DART_PRECOMPILED_RUNTIME)
+  Isolate* result = CreateWithinExistingIsolateGroupAOT(
+      reinterpret_cast<Isolate*>(parent)->group(), name, &error);
+#else
+  Isolate* result = CreateWithinExistingIsolateGroup(
+      reinterpret_cast<Isolate*>(parent)->group(), name, &error);
+#endif
+  if (error != nullptr) {
+    OS::PrintErr("CreateTestIsolateInGroup failed: %s\n", error);
+    free(error);
+  }
+  EXPECT(result != nullptr);
+  return Api::CastIsolate(result);
+}
+
 struct TestLibEntry {
   const char* url;
   const char* source;
diff --git a/runtime/vm/unit_test.h b/runtime/vm/unit_test.h
index 7dce889..21fc9cb 100644
--- a/runtime/vm/unit_test.h
+++ b/runtime/vm/unit_test.h
@@ -361,6 +361,11 @@
   static Dart_Isolate CreateTestIsolate(const char* name = nullptr,
                                         void* isolate_group_data = nullptr,
                                         void* isolate_data = nullptr);
+  static Dart_Isolate CreateTestIsolateInGroup(const char* name,
+                                               Dart_Isolate parent,
+                                               void* group_data = nullptr,
+                                               void* isolate_data = nullptr);
+
   static Dart_Handle library_handler(Dart_LibraryTag tag,
                                      Dart_Handle library,
                                      Dart_Handle url);
diff --git a/runtime/vm/visitor.cc b/runtime/vm/visitor.cc
index 8274cc8..145dd06 100644
--- a/runtime/vm/visitor.cc
+++ b/runtime/vm/visitor.cc
@@ -11,6 +11,6 @@
 ObjectPointerVisitor::ObjectPointerVisitor(IsolateGroup* isolate_group)
     : isolate_group_(isolate_group),
       gc_root_type_("unknown"),
-      shared_class_table_(isolate_group->class_table()) {}
+      shared_class_table_(isolate_group->shared_class_table()) {}
 
 }  // namespace dart
diff --git a/sdk_nnbd/lib/_internal/js_dev_runtime/patch/collection_patch.dart b/sdk_nnbd/lib/_internal/js_dev_runtime/patch/collection_patch.dart
index c10f04f..d9709b9 100644
--- a/sdk_nnbd/lib/_internal/js_dev_runtime/patch/collection_patch.dart
+++ b/sdk_nnbd/lib/_internal/js_dev_runtime/patch/collection_patch.dart
@@ -578,30 +578,3 @@
         iterator);
   }
 }
-
-@patch
-abstract class _SplayTree<K, Node extends _SplayTreeNode<K>> {
-  @patch
-  Node _splayMin(Node node) {
-    var current = node;
-    while (current.left != null) {
-      var left = current.left!;
-      current.left = left.right;
-      left.right = current;
-      current = left as Node;
-    }
-    return current;
-  }
-
-  @patch
-  Node _splayMax(Node node) {
-    var current = node;
-    while (current.right != null) {
-      var right = current.right!;
-      current.right = right.left;
-      right.left = current;
-      current = right as Node;
-    }
-    return current;
-  }
-}
diff --git a/sdk_nnbd/lib/_internal/js_runtime/lib/collection_patch.dart b/sdk_nnbd/lib/_internal/js_runtime/lib/collection_patch.dart
index 592b8c2..2b8bd25 100644
--- a/sdk_nnbd/lib/_internal/js_runtime/lib/collection_patch.dart
+++ b/sdk_nnbd/lib/_internal/js_runtime/lib/collection_patch.dart
@@ -1714,30 +1714,3 @@
     }
   }
 }
-
-@patch
-abstract class _SplayTree<K, Node extends _SplayTreeNode<K>> {
-  @patch
-  Node _splayMin(Node node) {
-    Node current = node;
-    while (current.left != null) {
-      Node left = current.left as Node;
-      current.left = left.right;
-      left.right = current;
-      current = left;
-    }
-    return current;
-  }
-
-  @patch
-  Node _splayMax(Node node) {
-    Node current = node;
-    while (current.right != null) {
-      Node right = current.right as Node;
-      current.right = right.left;
-      right.left = current;
-      current = right;
-    }
-    return current;
-  }
-}
diff --git a/sdk_nnbd/lib/_internal/vm/lib/collection_patch.dart b/sdk_nnbd/lib/_internal/vm/lib/collection_patch.dart
index dcb8fa1..1307fb7 100644
--- a/sdk_nnbd/lib/_internal/vm/lib/collection_patch.dart
+++ b/sdk_nnbd/lib/_internal/vm/lib/collection_patch.dart
@@ -13,6 +13,10 @@
 
 import "dart:typed_data" show Uint32List;
 
+class _TypeTest<T> {
+  bool test(v) => v is T;
+}
+
 /// These are the additional parts of this patch library:
 // part "compact_hash.dart";
 
@@ -915,35 +919,3 @@
   @patch
   factory LinkedHashSet.identity() => new _CompactLinkedIdentityHashSet<E>();
 }
-
-@patch
-abstract class _SplayTree<K, Node extends _SplayTreeNode<K>> {
-  // We override _splayMin and _splayMax to optimize type-checks.
-  @patch
-  Node _splayMin(Node node) {
-    Node current = node;
-    Object? nextLeft = current.left;
-    while (nextLeft != null) {
-      Node left = internal.unsafeCast<Node>(nextLeft);
-      current.left = left.right;
-      left.right = current;
-      current = left;
-      nextLeft = current.left;
-    }
-    return current;
-  }
-
-  @patch
-  Node _splayMax(Node node) {
-    Node current = node;
-    Object? nextRight = current.right;
-    while (nextRight != null) {
-      Node right = internal.unsafeCast<Node>(nextRight);
-      current.right = right.left;
-      right.left = current;
-      current = right;
-      nextRight = current.right;
-    }
-    return current;
-  }
-}
diff --git a/sdk_nnbd/lib/async/future.dart b/sdk_nnbd/lib/async/future.dart
index 30748ca..3de7374 100644
--- a/sdk_nnbd/lib/async/future.dart
+++ b/sdk_nnbd/lib/async/future.dart
@@ -311,7 +311,7 @@
    * later time that isn't necessarily after a known fixed duration.
    */
   factory Future.delayed(Duration duration, [FutureOr<T> computation()?]) {
-    if (computation == null && null is! T) {
+    if (computation == null && const <Null>[] is! List<T>) {
       throw ArgumentError.value(
           null, "computation", "The type parameter is not nullable");
     }
diff --git a/sdk_nnbd/lib/async/stream.dart b/sdk_nnbd/lib/async/stream.dart
index b0d41f4..241c706 100644
--- a/sdk_nnbd/lib/async/stream.dart
+++ b/sdk_nnbd/lib/async/stream.dart
@@ -263,7 +263,7 @@
    */
   factory Stream.periodic(Duration period,
       [T computation(int computationCount)?]) {
-    if (computation == null && null is! T) {
+    if (computation == null && const <Null>[] is! List<T>) {
       throw ArgumentError.value(null, "computation",
           "Must not be omitted when the event type is non-nullable");
     }
diff --git a/sdk_nnbd/lib/collection/splay_tree.dart b/sdk_nnbd/lib/collection/splay_tree.dart
index b81a034..a019b64 100644
--- a/sdk_nnbd/lib/collection/splay_tree.dart
+++ b/sdk_nnbd/lib/collection/splay_tree.dart
@@ -8,36 +8,27 @@
 
 /// A node in a splay tree. It holds the sorting key and the left
 /// and right children in the tree.
-class _SplayTreeNode<K> {
-  // The key is nullable to be able to create a dummy node.
-  final K? _key;
+class _SplayTreeNode<K, Node extends _SplayTreeNode<K, Node>> {
+  final K key;
 
-  _SplayTreeNode<K>? left;
-  _SplayTreeNode<K>? right;
+  Node? left;
+  Node? right;
 
-  _SplayTreeNode(this._key);
+  _SplayTreeNode(this.key);
+}
 
-  K get key {
-    // TODO(dartbug.com/40892): replace with '_key as K'
-    K? localKey = _key;
-    return (localKey != null) ? localKey : localKey as K;
-  }
+/// A node in a splay tree based set.
+class _SplayTreeSetNode<K> extends _SplayTreeNode<K, _SplayTreeSetNode<K>> {
+  _SplayTreeSetNode(K key) : super(key);
 }
 
 /// A node in a splay tree based map.
 ///
 /// A [_SplayTreeNode] that also contains a value
-class _SplayTreeMapNode<K, V> extends _SplayTreeNode<K> {
-  // The value is nullable to be able to create a dummy node.
-  V? _value;
-
-  _SplayTreeMapNode(K? key, this._value) : super(key);
-
-  V get value {
-    // TODO(dartbug.com/40892): replace with '_value as V'
-    V? localValue = _value;
-    return (localValue != null) ? localValue : localValue as V;
-  }
+class _SplayTreeMapNode<K, V>
+    extends _SplayTreeNode<K, _SplayTreeMapNode<K, V>> {
+  V value;
+  _SplayTreeMapNode(K key, this.value) : super(key);
 }
 
 /// A splay tree is a self-balancing binary search tree.
@@ -46,16 +37,12 @@
 /// are quick to access again.
 /// It performs basic operations such as insertion, look-up and
 /// removal, in O(log(n)) amortized time.
-abstract class _SplayTree<K, Node extends _SplayTreeNode<K>> {
+abstract class _SplayTree<K, Node extends _SplayTreeNode<K, Node>> {
   // The root node of the splay tree. It will contain either the last
   // element inserted or the last element looked up.
   Node? get _root;
   set _root(Node? newValue);
 
-  // The dummy node used when performing a splay on the tree. Reusing it
-  // avoids allocating a node each time a splay is performed.
-  Node get _dummy;
-
   // Number of elements in the splay tree.
   int _count = 0;
 
@@ -71,14 +58,11 @@
   int _splayCount = 0;
 
   /// The comparator that is used for this splay tree.
-  Comparator<K> get _comparator;
+  Comparator<K> get _compare;
 
   /// The predicate to determine that a given object is a valid key.
   _Predicate get _validKey;
 
-  /// Comparison used to compare keys.
-  int _compare(K key1, K key2);
-
   /// Perform the splay operation for the given key. Moves the node with
   /// the given key to the top of the tree.  If no node has the given
   /// key, the last node on the search path is moved to the top of the
@@ -90,59 +74,80 @@
   int _splay(K key) {
     if (_root == null) return -1;
 
-    // The right child of the dummy node will hold
-    // the L tree of the algorithm.  The left child of the dummy node
-    // will hold the R tree of the algorithm.  Using a dummy node, left
-    // and right will always be nodes and we avoid special cases.
-    Node left = _dummy;
-    Node right = _dummy;
-    Node current = _root!;
+    // The right and newTreeRight variables start out null, and are set
+    // after the first move left.  The right node is the destination
+    // for subsequent left rebalances, and newTreeRight holds the left
+    // child of the final tree.  The newTreeRight variable is set at most
+    // once, after the first move left, and is null iff right is null.
+    // The left and newTreeLeft variables play the corresponding role for
+    // right rebalances.
+    Node? right;
+    Node? newTreeRight;
+    Node? left;
+    Node? newTreeLeft;
+    var current = _root!;
+    // Hoist the field read out of the loop.
+    var compare = _compare;
     int comp;
     while (true) {
-      comp = _compare(current.key, key);
+      comp = compare(current.key, key);
       if (comp > 0) {
-        if (current.left == null) break;
-        comp = _compare(current.left!.key, key);
+        var currentLeft = current.left;
+        if (currentLeft == null) break;
+        comp = compare(currentLeft.key, key);
         if (comp > 0) {
           // Rotate right.
-          Node tmp = current.left as Node;
-          current.left = tmp.right;
-          tmp.right = current;
-          current = tmp;
-          if (current.left == null) break;
+          current.left = currentLeft.right;
+          currentLeft.right = current;
+          current = currentLeft;
+          currentLeft = current.left;
+          if (currentLeft == null) break;
         }
         // Link right.
-        right.left = current;
+        if (right == null) {
+          // First left rebalance, store the eventual right child
+          newTreeRight = current;
+        } else {
+          right.left = current;
+        }
         right = current;
-        current = current.left as Node;
+        current = currentLeft;
       } else if (comp < 0) {
-        if (current.right == null) break;
-        comp = _compare(current.right!.key, key);
+        var currentRight = current.right;
+        if (currentRight == null) break;
+        comp = compare(currentRight.key, key);
         if (comp < 0) {
           // Rotate left.
-          Node tmp = current.right as Node;
-          current.right = tmp.left;
-          tmp.left = current;
-          current = tmp;
-          if (current.right == null) break;
+          current.right = currentRight.left;
+          currentRight.left = current;
+          current = currentRight;
+          currentRight = current.right;
+          if (currentRight == null) break;
         }
         // Link left.
-        left.right = current;
+        if (left == null) {
+          // First right rebalance, store the eventual left child
+          newTreeLeft = current;
+        } else {
+          left.right = current;
+        }
         left = current;
-        current = current.right as Node;
+        current = currentRight;
       } else {
         break;
       }
     }
     // Assemble.
-    left.right = current.left;
-    right.left = current.right;
-    current.left = _dummy.right;
-    current.right = _dummy.left;
+    if (left != null) {
+      left.right = current.left;
+      current.left = newTreeLeft;
+    }
+    if (right != null) {
+      right.left = current.right;
+      current.right = newTreeRight;
+    }
     _root = current;
 
-    _dummy.right = null;
-    _dummy.left = null;
     _splayCount++;
     return comp;
   }
@@ -151,31 +156,57 @@
   // anchored at [node].
   // and that node is returned. It should replace the reference to [node]
   // in any parent tree or root pointer.
-  external Node _splayMin(Node node);
+  Node _splayMin(Node node) {
+    var current = node;
+    var nextLeft = current.left;
+    while (nextLeft != null) {
+      var left = nextLeft;
+      current.left = left.right;
+      left.right = current;
+      current = left;
+      nextLeft = current.left;
+    }
+    return current;
+  }
 
   // Emulates splaying with a key that is greater than any in the subtree
   // anchored at [node].
   // After this, the largest element in the tree is the root of the subtree,
   // and that node is returned. It should replace the reference to [node]
   // in any parent tree or root pointer.
-  external Node _splayMax(Node node);
+  Node _splayMax(Node node) {
+    var current = node;
+    var nextRight = current.right;
+    while (nextRight != null) {
+      var right = nextRight;
+      current.right = right.left;
+      right.left = current;
+      current = right;
+      nextRight = current.right;
+    }
+    return current;
+  }
 
   Node? _remove(K key) {
     if (_root == null) return null;
     int comp = _splay(key);
     if (comp != 0) return null;
-    Node result = _root!;
+    var root = _root!;
+    var result = root;
+    var left = root.left;
     _count--;
     // assert(_count >= 0);
-    if (_root!.left == null) {
-      _root = _root!.right as Node?;
+    if (left == null) {
+      _root = root.right;
     } else {
-      Node? right = _root!.right as Node?;
+      var right = root.right;
       // Splay to make sure that the new root has an empty right child.
-      _root = _splayMax(_root!.left as Node);
+      root = _splayMax(left);
+
       // Insert the original right child as the right child of the new
       // root.
-      _root!.right = right;
+      root.right = right;
+      _root = root;
     }
     _modificationCount++;
     return result;
@@ -188,32 +219,35 @@
   void _addNewRoot(Node node, int comp) {
     _count++;
     _modificationCount++;
-    if (_root == null) {
+    var root = _root;
+    if (root == null) {
       _root = node;
       return;
     }
     // assert(_count >= 0);
     if (comp < 0) {
-      node.left = _root;
-      node.right = _root!.right;
-      _root!.right = null;
+      node.left = root;
+      node.right = root.right;
+      root.right = null;
     } else {
-      node.right = _root;
-      node.left = _root!.left;
-      _root!.left = null;
+      node.right = root;
+      node.left = root.left;
+      root.left = null;
     }
     _root = node;
   }
 
   Node? get _first {
-    if (_root == null) return null;
-    _root = _splayMin(_root!);
+    var root = _root;
+    if (root == null) return null;
+    _root = _splayMin(root);
     return _root;
   }
 
   Node? get _last {
-    if (_root == null) return null;
-    _root = _splayMax(_root!);
+    var root = _root;
+    if (root == null) return null;
+    _root = _splayMax(root);
     return _root;
   }
 
@@ -224,19 +258,14 @@
   }
 }
 
-class _TypeTest<T> {
-  bool test(v) => v is T;
-}
-
-int _dynamicCompare(dynamic a, dynamic b) =>
-    Comparable.compare(a as Comparable, b as Comparable);
+int _dynamicCompare(dynamic a, dynamic b) => Comparable.compare(a, b);
 
 Comparator<K> _defaultCompare<K>() {
   // If K <: Comparable, then we can just use Comparable.compare
   // with no casts.
   Object compare = Comparable.compare;
   if (compare is Comparator<K>) {
-    return compare as Comparator<K>;
+    return compare;
   }
   // Otherwise wrap and cast the arguments on each call.
   return _dynamicCompare;
@@ -266,15 +295,14 @@
 class SplayTreeMap<K, V> extends _SplayTree<K, _SplayTreeMapNode<K, V>>
     with MapMixin<K, V> {
   _SplayTreeMapNode<K, V>? _root;
-  final _SplayTreeMapNode<K, V> _dummy = _SplayTreeMapNode<K, V>(null, null);
 
-  Comparator<K> _comparator;
+  Comparator<K> _compare;
   _Predicate _validKey;
 
   SplayTreeMap(
       [int Function(K key1, K key2)? compare,
       bool Function(dynamic potentialKey)? isValidKey])
-      : _comparator = compare ?? _defaultCompare<K>(),
+      : _compare = compare ?? _defaultCompare<K>(),
         _validKey = isValidKey ?? ((dynamic v) => v is K);
 
   /// Creates a [SplayTreeMap] that contains all key/value pairs of [other].
@@ -284,9 +312,12 @@
   factory SplayTreeMap.from(Map<dynamic, dynamic> other,
       [int Function(K key1, K key2)? compare,
       bool Function(dynamic potentialKey)? isValidKey]) {
+    if (other is Map<K, V>) {
+      return SplayTreeMap<K, V>.of(other, compare, isValidKey);
+    }
     SplayTreeMap<K, V> result = SplayTreeMap<K, V>(compare, isValidKey);
     other.forEach((dynamic k, dynamic v) {
-      result[k as K] = v as V;
+      result[k] = v;
     });
     return result;
   }
@@ -335,12 +366,10 @@
     return map;
   }
 
-  int _compare(K key1, K key2) => _comparator(key1, key2);
-
   V? operator [](Object? key) {
     if (!_validKey(key)) return null;
     if (_root != null) {
-      int comp = _splay(key as K);
+      int comp = _splay(key as dynamic);
       if (comp == 0) {
         return _root!.value;
       }
@@ -350,7 +379,7 @@
 
   V? remove(Object? key) {
     if (!_validKey(key)) return null;
-    _SplayTreeMapNode<K, V>? mapRoot = _remove(key as K);
+    _SplayTreeMapNode<K, V>? mapRoot = _remove(key as dynamic);
     if (mapRoot != null) return mapRoot.value;
     return null;
   }
@@ -361,7 +390,7 @@
     // the key to the root of the tree.
     int comp = _splay(key);
     if (comp == 0) {
-      _root!._value = value;
+      _root!.value = value;
       return;
     }
     _addNewRoot(_SplayTreeMapNode(key, value), comp);
@@ -401,9 +430,10 @@
   bool get isNotEmpty => !isEmpty;
 
   void forEach(void f(K key, V value)) {
-    Iterator<_SplayTreeNode<K>> nodes = _SplayTreeNodeIterator<K>(this);
+    Iterator<_SplayTreeMapNode<K, V>> nodes =
+        _SplayTreeNodeIterator<K, _SplayTreeMapNode<K, V>>(this);
     while (nodes.moveNext()) {
-      _SplayTreeMapNode<K, V> node = nodes.current as _SplayTreeMapNode<K, V>;
+      _SplayTreeMapNode<K, V> node = nodes.current;
       f(node.key, node.value);
     }
   }
@@ -417,21 +447,21 @@
   }
 
   bool containsKey(Object? key) {
-    return _validKey(key) && _splay(key as K) == 0;
+    return _validKey(key) && _splay(key as dynamic) == 0;
   }
 
   bool containsValue(Object? value) {
     int initialSplayCount = _splayCount;
-    bool visit(_SplayTreeMapNode? node) {
+    bool visit(_SplayTreeMapNode<K, V>? node) {
       while (node != null) {
         if (node.value == value) return true;
         if (initialSplayCount != _splayCount) {
           throw ConcurrentModificationError(this);
         }
-        if (node.right != null && visit(node.right as _SplayTreeMapNode)) {
+        if (node.right != null && visit(node.right)) {
           return true;
         }
-        node = node.left as _SplayTreeMapNode?;
+        node = node.left;
       }
       return false;
     }
@@ -439,7 +469,8 @@
     return visit(_root);
   }
 
-  Iterable<K> get keys => _SplayTreeKeyIterable<K>(this);
+  Iterable<K> get keys =>
+      _SplayTreeKeyIterable<K, _SplayTreeMapNode<K, V>>(this);
 
   Iterable<V> get values => _SplayTreeValueIterable<K, V>(this);
 
@@ -462,12 +493,14 @@
     if (_root == null) return null;
     int comp = _splay(key);
     if (comp < 0) return _root!.key;
-    _SplayTreeNode<K>? node = _root!.left;
+    _SplayTreeMapNode<K, V>? node = _root!.left;
     if (node == null) return null;
-    while (node!.right != null) {
-      node = node.right;
+    var nodeRight = node.right;
+    while (nodeRight != null) {
+      node = nodeRight;
+      nodeRight = node.right;
     }
-    return node.key;
+    return node!.key;
   }
 
   /// Get the first key in the map that is strictly larger than [key]. Returns
@@ -477,17 +510,20 @@
     if (_root == null) return null;
     int comp = _splay(key);
     if (comp > 0) return _root!.key;
-    _SplayTreeNode<K>? node = _root!.right;
+    _SplayTreeMapNode<K, V>? node = _root!.right;
     if (node == null) return null;
-    while (node!.left != null) {
-      node = node.left;
+    var nodeLeft = node.left;
+    while (nodeLeft != null) {
+      node = nodeLeft;
+      nodeLeft = node.left;
     }
-    return node.key;
+    return node!.key;
   }
 }
 
-abstract class _SplayTreeIterator<K, T> implements Iterator<T> {
-  final _SplayTree<K, _SplayTreeNode<K>> _tree;
+abstract class _SplayTreeIterator<K, Node extends _SplayTreeNode<K, Node>, T>
+    implements Iterator<T> {
+  final _SplayTree<K, Node> _tree;
 
   /// Worklist of nodes to visit.
   ///
@@ -497,7 +533,7 @@
   /// the nodes of a full traversal.
   ///
   /// Only valid as long as the original tree isn't reordered.
-  final List<_SplayTreeNode<K>> _workList = <_SplayTreeNode<K>>[];
+  final List<Node> _workList = [];
 
   /// Original modification counter of [_tree].
   ///
@@ -514,16 +550,16 @@
   int _splayCount;
 
   /// Current node.
-  _SplayTreeNode<K>? _currentNode;
+  Node? _currentNode;
 
-  _SplayTreeIterator(_SplayTree<K, _SplayTreeNode<K>> tree)
+  _SplayTreeIterator(_SplayTree<K, Node> tree)
       : _tree = tree,
         _modificationCount = tree._modificationCount,
         _splayCount = tree._splayCount {
     _findLeftMostDescendent(tree._root);
   }
 
-  _SplayTreeIterator.startAt(_SplayTree<K, _SplayTreeNode<K>> tree, K startKey)
+  _SplayTreeIterator.startAt(_SplayTree<K, Node> tree, K startKey)
       : _tree = tree,
         _modificationCount = tree._modificationCount,
         _splayCount = -1 {
@@ -539,11 +575,12 @@
   }
 
   T get current {
-    if (_currentNode == null) return null as T;
-    return _getValue(_currentNode!);
+    var node = _currentNode;
+    if (node == null) return null as T;
+    return _getValue(node);
   }
 
-  void _findLeftMostDescendent(_SplayTreeNode<K>? node) {
+  void _findLeftMostDescendent(Node? node) {
     while (node != null) {
       _workList.add(node);
       node = node.left;
@@ -556,16 +593,12 @@
   /// If the key-set changes, iteration is aborted before getting
   /// here, so we know that the keys are the same as before, it's
   /// only the tree that has been reordered.
-  void _rebuildWorkList(_SplayTreeNode<K> currentNode) {
+  void _rebuildWorkList(Node currentNode) {
     assert(_workList.isNotEmpty);
     _workList.clear();
-    if (currentNode == null) {
-      _findLeftMostDescendent(_tree._root);
-    } else {
-      _tree._splay(currentNode.key);
-      _findLeftMostDescendent(_tree._root!.right);
-      assert(_workList.isNotEmpty);
-    }
+    _tree._splay(currentNode.key);
+    _findLeftMostDescendent(_tree._root!.right);
+    assert(_workList.isNotEmpty);
   }
 
   bool moveNext() {
@@ -589,20 +622,21 @@
     return true;
   }
 
-  T _getValue(_SplayTreeNode<K> node);
+  T _getValue(Node node);
 }
 
-class _SplayTreeKeyIterable<K> extends EfficientLengthIterable<K> {
-  _SplayTree<K, _SplayTreeNode<K>> _tree;
+class _SplayTreeKeyIterable<K, Node extends _SplayTreeNode<K, Node>>
+    extends EfficientLengthIterable<K> {
+  _SplayTree<K, Node> _tree;
   _SplayTreeKeyIterable(this._tree);
   int get length => _tree._count;
   bool get isEmpty => _tree._count == 0;
-  Iterator<K> get iterator => _SplayTreeKeyIterator<K>(_tree);
+  Iterator<K> get iterator => _SplayTreeKeyIterator<K, Node>(_tree);
 
   Set<K> toSet() {
-    SplayTreeSet<K> set = SplayTreeSet<K>(_tree._comparator, _tree._validKey);
+    SplayTreeSet<K> set = SplayTreeSet<K>(_tree._compare, _tree._validKey);
     set._count = _tree._count;
-    set._root = set._copyNode(_tree._root);
+    set._root = set._copyNode<Node>(_tree._root);
     return set;
   }
 }
@@ -615,26 +649,24 @@
   Iterator<V> get iterator => _SplayTreeValueIterator<K, V>(_map);
 }
 
-class _SplayTreeKeyIterator<K> extends _SplayTreeIterator<K, K> {
-  _SplayTreeKeyIterator(_SplayTree<K, _SplayTreeNode<K>> map) : super(map);
-  K _getValue(_SplayTreeNode<K> node) => node.key;
+class _SplayTreeKeyIterator<K, Node extends _SplayTreeNode<K, Node>>
+    extends _SplayTreeIterator<K, Node, K> {
+  _SplayTreeKeyIterator(_SplayTree<K, Node> map) : super(map);
+  K _getValue(Node node) => node.key;
 }
 
-class _SplayTreeValueIterator<K, V> extends _SplayTreeIterator<K, V> {
+class _SplayTreeValueIterator<K, V>
+    extends _SplayTreeIterator<K, _SplayTreeMapNode<K, V>, V> {
   _SplayTreeValueIterator(SplayTreeMap<K, V> map) : super(map);
-  V _getValue(_SplayTreeNode<K> node) {
-    _SplayTreeMapNode<K, V> mapNode = node as _SplayTreeMapNode<K, V>;
-    return mapNode.value;
-  }
+  V _getValue(_SplayTreeMapNode<K, V> node) => node.value;
 }
 
-class _SplayTreeNodeIterator<K>
-    extends _SplayTreeIterator<K, _SplayTreeNode<K>> {
-  _SplayTreeNodeIterator(_SplayTree<K, _SplayTreeNode<K>> tree) : super(tree);
-  _SplayTreeNodeIterator.startAt(
-      _SplayTree<K, _SplayTreeNode<K>> tree, K startKey)
+class _SplayTreeNodeIterator<K, Node extends _SplayTreeNode<K, Node>>
+    extends _SplayTreeIterator<K, Node, Node> {
+  _SplayTreeNodeIterator(_SplayTree<K, Node> tree) : super(tree);
+  _SplayTreeNodeIterator.startAt(_SplayTree<K, Node> tree, K startKey)
       : super.startAt(tree, startKey);
-  _SplayTreeNode<K> _getValue(_SplayTreeNode<K> node) => node;
+  Node _getValue(Node node) => node;
 }
 
 /// A [Set] of objects that can be ordered relative to each other.
@@ -651,12 +683,11 @@
 /// [Comparable], and are compared using their [Comparable.compareTo] method.
 /// Non-comparable objects (including `null`) will not work as an element
 /// in that case.
-class SplayTreeSet<E> extends _SplayTree<E, _SplayTreeNode<E>>
+class SplayTreeSet<E> extends _SplayTree<E, _SplayTreeSetNode<E>>
     with IterableMixin<E>, SetMixin<E> {
-  _SplayTreeNode<E>? _root;
-  final _SplayTreeNode<E> _dummy = _SplayTreeNode<E>(null);
+  _SplayTreeSetNode<E>? _root;
 
-  Comparator<E> _comparator;
+  Comparator<E> _compare;
   _Predicate _validKey;
 
   /// Create a new [SplayTreeSet] with the given compare function.
@@ -684,7 +715,7 @@
   SplayTreeSet(
       [int Function(E key1, E key2)? compare,
       bool Function(dynamic potentialKey)? isValidKey])
-      : _comparator = compare ?? _defaultCompare<E>(),
+      : _compare = compare ?? _defaultCompare<E>(),
         _validKey = isValidKey ?? ((dynamic v) => v is E);
 
   /// Creates a [SplayTreeSet] that contains all [elements].
@@ -703,10 +734,12 @@
   factory SplayTreeSet.from(Iterable elements,
       [int Function(E key1, E key2)? compare,
       bool Function(dynamic potentialKey)? isValidKey]) {
+    if (elements is Iterable<E>) {
+      return SplayTreeSet<E>.of(elements, compare, isValidKey);
+    }
     SplayTreeSet<E> result = SplayTreeSet<E>(compare, isValidKey);
-    for (final element in elements) {
-      E e = element as E;
-      result.add(e);
+    for (var element in elements) {
+      result.add(element as dynamic);
     }
     return result;
   }
@@ -722,14 +755,14 @@
       SplayTreeSet(compare, isValidKey)..addAll(elements);
 
   Set<T> _newSet<T>() =>
-      SplayTreeSet<T>((T a, T b) => _comparator(a as E, b as E), _validKey);
+      SplayTreeSet<T>((T a, T b) => _compare(a as E, b as E), _validKey);
 
   Set<R> cast<R>() => Set.castFrom<E, R>(this, newSet: _newSet);
-  int _compare(E e1, E e2) => _comparator(e1, e2);
 
   // From Iterable.
 
-  Iterator<E> get iterator => _SplayTreeKeyIterator<E>(this);
+  Iterator<E> get iterator =>
+      _SplayTreeKeyIterator<E, _SplayTreeSetNode<E>>(this);
 
   int get length => _count;
   bool get isEmpty => _root == null;
@@ -759,7 +792,7 @@
   bool add(E element) {
     int compare = _splay(element);
     if (compare == 0) return false;
-    _addNewRoot(_SplayTreeNode(element), compare);
+    _addNewRoot(_SplayTreeSetNode(element), compare);
     return true;
   }
 
@@ -772,7 +805,7 @@
     for (E element in elements) {
       int compare = _splay(element);
       if (compare != 0) {
-        _addNewRoot(_SplayTreeNode(element), compare);
+        _addNewRoot(_SplayTreeSetNode(element), compare);
       }
     }
   }
@@ -785,7 +818,7 @@
 
   void retainAll(Iterable<Object?> elements) {
     // Build a set with the same sense of equality as this set.
-    SplayTreeSet<E> retainSet = SplayTreeSet<E>(_comparator, _validKey);
+    SplayTreeSet<E> retainSet = SplayTreeSet<E>(_compare, _validKey);
     int modificationCount = _modificationCount;
     for (Object? object in elements) {
       if (modificationCount != _modificationCount) {
@@ -813,7 +846,7 @@
   }
 
   Set<E> intersection(Set<Object?> other) {
-    Set<E> result = SplayTreeSet<E>(_comparator, _validKey);
+    Set<E> result = SplayTreeSet<E>(_compare, _validKey);
     for (E element in this) {
       if (other.contains(element)) result.add(element);
     }
@@ -821,7 +854,7 @@
   }
 
   Set<E> difference(Set<Object?> other) {
-    Set<E> result = SplayTreeSet<E>(_comparator, _validKey);
+    Set<E> result = SplayTreeSet<E>(_compare, _validKey);
     for (E element in this) {
       if (!other.contains(element)) result.add(element);
     }
@@ -833,19 +866,46 @@
   }
 
   SplayTreeSet<E> _clone() {
-    var set = SplayTreeSet<E>(_comparator, _validKey);
+    var set = SplayTreeSet<E>(_compare, _validKey);
     set._count = _count;
-    set._root = _copyNode(_root);
+    set._root = _copyNode<_SplayTreeSetNode<E>>(_root);
     return set;
   }
 
   // Copies the structure of a SplayTree into a new similar structure.
   // Works on _SplayTreeMapNode as well, but only copies the keys,
-  _SplayTreeNode<E>? _copyNode(_SplayTreeNode<E>? node) {
+  _SplayTreeSetNode<E>? _copyNode<Node extends _SplayTreeNode<E, Node>>(
+      Node? node) {
     if (node == null) return null;
-    return _SplayTreeNode<E>(node.key)
-      ..left = _copyNode(node.left)
-      ..right = _copyNode(node.right);
+    // Given a source node and a destination node, copy the left
+    // and right subtrees of the source node into the destination node.
+    // The left subtree is copied recursively, but the right spine
+    // of every subtree is copied iteratively.
+    void copyChildren(Node node, _SplayTreeSetNode<E> dest) {
+      Node? left;
+      Node? right;
+      do {
+        left = node.left;
+        right = node.right;
+        if (left != null) {
+          var newLeft = _SplayTreeSetNode<E>(left.key);
+          dest.left = newLeft;
+          // Recursively copy the left tree.
+          copyChildren(left, newLeft);
+        }
+        if (right != null) {
+          var newRight = _SplayTreeSetNode<E>(right.key);
+          dest.right = newRight;
+          // Set node and dest to copy the right tree iteratively.
+          node = right;
+          dest = newRight;
+        }
+      } while (right != null);
+    }
+
+    var result = _SplayTreeSetNode<E>(node.key);
+    copyChildren(node, result);
+    return result;
   }
 
   void clear() {
diff --git a/tests/language/nnbd/try_catch/default_catch_type_error_test.dart b/tests/language/nnbd/try_catch/default_catch_type_error_test.dart
new file mode 100644
index 0000000..73b3b94
--- /dev/null
+++ b/tests/language/nnbd/try_catch/default_catch_type_error_test.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+void main() {
+  try {} catch (error) {
+    error.notAMethodOnObject();
+    //    ^^^^^^^^^^^^^^^^^^
+    // [analyzer] STATIC_TYPE_WARNING.UNDEFINED_METHOD
+    // [cfe] The method 'notAMethodOnObject' isn't defined for the class 'Object'.
+    _takesObject(error);
+  }
+}
+
+void _takesObject(Object o) {}
diff --git a/tests/lib/async/stream_controller_async_test.dart b/tests/lib/async/stream_controller_async_test.dart
index 70223fe..dc83af6 100644
--- a/tests/lib/async/stream_controller_async_test.dart
+++ b/tests/lib/async/stream_controller_async_test.dart
@@ -33,9 +33,9 @@
   test("StreamController.fold throws", () {
     StreamController c = new StreamController();
     Stream stream = c.stream.asBroadcastStream(onCancel: cancelSub);
-    stream.fold(0, (a, b) {
+    Future<int?>.value(stream.fold(0, (a, b) {
       throw "Fnyf!";
-    }).catchError(expectAsync((error) {
+    })).catchError(expectAsync((error) {
       Expect.equals("Fnyf!", error);
     }));
     c.add(42);
@@ -57,9 +57,9 @@
   test("Single-subscription StreamController.fold throws", () {
     StreamController c = new StreamController();
     Stream stream = c.stream;
-    stream.fold(0, (a, b) {
+    Future<int?>.value(stream.fold(0, (a, b) {
       throw "Fnyf!";
-    }).catchError(expectAsync((e) {
+    })).catchError(expectAsync((e) {
       Expect.equals("Fnyf!", e);
     }));
     c.add(42);
@@ -669,8 +669,8 @@
   });
 }
 
-void testSink({
-    required bool sync, required bool broadcast, required bool asBroadcast}) {
+void testSink(
+    {required bool sync, required bool broadcast, required bool asBroadcast}) {
   String type =
       "${sync ? "S" : "A"}${broadcast ? "B" : "S"}${asBroadcast ? "aB" : ""}";
   test("$type-controller-sink", () {
diff --git a/tests/lib/async/stream_join_test.dart b/tests/lib/async/stream_join_test.dart
index 6e3a98b..49077aa 100644
--- a/tests/lib/async/stream_join_test.dart
+++ b/tests/lib/async/stream_join_test.dart
@@ -52,8 +52,7 @@
 
   test("join-error", () {
     StreamController c = new StreamController();
-    c.stream
-        .join("X")
+    Future<String?>.value(c.stream.join("X"))
         .catchError(expectAsync((String s) => expect(s, equals("BAD!"))));
     c.add(new Foo("foo"));
     c.add(new Foo("bar"));
diff --git a/tests/lib/isolate/message3_test.dart b/tests/lib/isolate/message3_test.dart
index 0bc63ff..f1f11bf 100644
--- a/tests/lib/isolate/message3_test.dart
+++ b/tests/lib/isolate/message3_test.dart
@@ -60,7 +60,11 @@
 }
 
 class E {
+  // Make sure E.fun is not removed by the tree shaker, as this test verifies
+  // that an object with a tear-off in E.fun cannot be sent.
+  @pragma("vm:entry-point")
   Function fun;
+
   E(this.fun);
 
   static fooFun() => 499;
diff --git a/tests/lib_2/isolate/message3_test.dart b/tests/lib_2/isolate/message3_test.dart
index aa3166c..6536b36 100644
--- a/tests/lib_2/isolate/message3_test.dart
+++ b/tests/lib_2/isolate/message3_test.dart
@@ -60,7 +60,11 @@
 }
 
 class E {
+  // Make sure E.fun is not removed by the tree shaker, as this test verifies
+  // that an object with a tear-off in E.fun cannot be sent.
+  @pragma("vm:entry-point")
   Function fun;
+
   E(this.fun);
 
   static fooFun() => 499;
diff --git a/tools/FAKE_COMMITS b/tools/FAKE_COMMITS
index 597f3da..decbb70 100644
--- a/tools/FAKE_COMMITS
+++ b/tools/FAKE_COMMITS
@@ -30,3 +30,4 @@
 Trigger bots
 Switch benchmark builders to Ubuntu 16.04
 Land a CL with no tryjobs, to validate a fix in copying approvals
+Confirm all approvals took effect
diff --git a/tools/VERSION b/tools/VERSION
index ab560b2..8909bf9 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -33,7 +33,7 @@
 MAJOR 2
 MINOR 9
 PATCH 0
-PRERELEASE 0
-PRERELEASE_PATCH dev
+PRERELEASE 1
+PRERELEASE_PATCH 0
 ABI_VERSION 32
 OLDEST_SUPPORTED_ABI_VERSION 32
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index ec2f704..e4602d8 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -630,6 +630,7 @@
           "--libraries-spec=sdk_nnbd/lib/libraries.json",
           "--platform-binaries=out/ReleaseX64NNBD/"
         ],
+        "timeout": 240,
         "host-checked": true
       }
     },
@@ -644,6 +645,7 @@
           "--libraries-spec=sdk_nnbd/lib/libraries.json",
           "--platform-binaries=xcodebuild/ReleaseX64NNBD/"
         ],
+        "timeout": 240,
         "host-checked": true
       }
     },
@@ -658,6 +660,7 @@
           "--libraries-spec=sdk_nnbd/lib/libraries.json",
           "--platform-binaries=out/ReleaseX64NNBD/"
         ],
+        "timeout": 240,
         "host-checked": true
       }
     },
@@ -672,6 +675,7 @@
           "--libraries-spec=sdk_nnbd/lib/libraries.json",
           "--platform-binaries=xcodebuild/ReleaseX64NNBD/"
         ],
+        "timeout": 240,
         "host-checked": true
       }
     },
@@ -1655,7 +1659,6 @@
           "arguments": [
             "-ndartkp-${sanitizer}-${system}-${mode}-${arch}",
             "vm",
-            "standalone",
             "standalone_2"
           ]
         }
@@ -3692,4 +3695,4 @@
     "linux": "buildtools/linux-x64/clang/bin/llvm-symbolizer",
     "macos": "buildtools/mac-x64/clang/bin/llvm-symbolizer"
   }
-}
\ No newline at end of file
+}