Version 2.12.0-132.0.dev

Merge commit 'c549ecf1e6900be9a49e7ba5931688a326666c40' into 'dev'
diff --git a/pkg/front_end/lib/src/fasta/kernel/combined_member_signature.dart b/pkg/front_end/lib/src/fasta/kernel/combined_member_signature.dart
index c4ff40d..5924d4a 100644
--- a/pkg/front_end/lib/src/fasta/kernel/combined_member_signature.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/combined_member_signature.dart
@@ -301,7 +301,15 @@
           "No member computed for index ${index} in ${members}");
       candidateType = _computeMemberType(thisType, target);
       if (!classBuilder.library.isNonNullableByDefault) {
-        DartType legacyErasure = rawLegacyErasure(candidateType);
+        DartType legacyErasure;
+        if (target == hierarchy.coreTypes.objectEquals) {
+          // In legacy code we special case `Object.==` to infer `dynamic`
+          // instead `Object!`.
+          legacyErasure = new FunctionType([const DynamicType()],
+              hierarchy.coreTypes.boolLegacyRawType, Nullability.legacy);
+        } else {
+          legacyErasure = rawLegacyErasure(candidateType);
+        }
         if (legacyErasure != null) {
           _neededLegacyErasureIndices ??= {};
           _neededLegacyErasureIndices.add(index);
@@ -611,11 +619,6 @@
     for (int i = 0; i < function.positionalParameters.length; i++) {
       VariableDeclaration parameter = function.positionalParameters[i];
       DartType parameterType = functionType.positionalParameters[i];
-      if (i == 0 && procedure == hierarchy.coreTypes.objectEquals) {
-        // In legacy code we special case `Object.==` to infer `dynamic`
-        // instead `Object!`.
-        parameterType = const DynamicType();
-      }
       positionalParameters.add(new VariableDeclaration(parameter.name,
           type: parameterType, isCovariant: parameter.isCovariant)
         ..isGenericCovariantImpl = parameter.isGenericCovariantImpl);
diff --git a/pkg/front_end/lib/src/fasta/kernel/constant_collection_builders.dart b/pkg/front_end/lib/src/fasta/kernel/constant_collection_builders.dart
index 8caaedc..f10419d 100644
--- a/pkg/front_end/lib/src/fasta/kernel/constant_collection_builders.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/constant_collection_builders.dart
@@ -53,6 +53,18 @@
         entries = spread.entries;
       } else if (spread is SetConstant) {
         entries = spread.entries;
+      } else if (evaluator.backend.isLoweredListConstant(spread)) {
+        entries = <Constant>[];
+        evaluator.backend.forEachLoweredListConstantElement(spread,
+            (Constant element) {
+          entries.add(element);
+        });
+      } else if (evaluator.backend.isLoweredSetConstant(constant)) {
+        entries = <Constant>[];
+        evaluator.backend.forEachLoweredSetConstantElement(spread,
+            (Constant element) {
+          entries.add(element);
+        });
       } else {
         // Not list or set in spread
         return evaluator.createErrorConstant(
@@ -176,26 +188,7 @@
       // Fully evaluated
       List<Constant> entries = parts.single;
       SetConstant result = new SetConstant(elementType, entries);
-      if (evaluator.desugarSets) {
-        final List<ConstantMapEntry> mapEntries =
-            new List<ConstantMapEntry>.filled(entries.length, null);
-        for (int i = 0; i < entries.length; ++i) {
-          mapEntries[i] =
-              new ConstantMapEntry(entries[i], evaluator.nullConstant);
-        }
-        Constant map = evaluator.lowerMapConstant(
-            new MapConstant(elementType, const NullType(), mapEntries));
-        return evaluator.lower(
-            result,
-            new InstanceConstant(
-                evaluator.unmodifiableSetMap.enclosingClass.reference, [
-              elementType
-            ], <Reference, Constant>{
-              evaluator.unmodifiableSetMap.getterReference: map
-            }));
-      } else {
-        return evaluator.lowerSetConstant(result);
-      }
+      return evaluator.lowerSetConstant(result);
     }
     List<Expression> sets = <Expression>[];
     for (Object part in parts) {
@@ -271,6 +264,13 @@
               entry.key, entry.value, spreadExpression, spreadExpression);
           if (error != null) return error;
         }
+      } else if (evaluator.backend.isLoweredMapConstant(spread)) {
+        AbortConstant error;
+        evaluator.backend.forEachLoweredMapConstantEntry(spread,
+            (Constant key, Constant value) {
+          error ??= addConstant(key, value, spreadExpression, spreadExpression);
+        });
+        if (error != null) return error;
       } else {
         // Not map in spread
         return evaluator.createErrorConstant(
diff --git a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
index c0c6a16..a92f302 100644
--- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
@@ -97,7 +97,6 @@
 
   transformLibraries(component.libraries, backend, environmentDefines,
       typeEnvironment, errorReporter, evaluationMode,
-      desugarSets: desugarSets,
       enableTripleShift: enableTripleShift,
       errorOnUnevaluatedConstant: errorOnUnevaluatedConstant,
       evaluateAnnotations: evaluateAnnotations);
@@ -112,18 +111,15 @@
     ErrorReporter errorReporter,
     EvaluationMode evaluationMode,
     {bool evaluateAnnotations,
-    bool desugarSets,
     bool enableTripleShift,
     bool errorOnUnevaluatedConstant}) {
   assert(evaluateAnnotations != null);
-  assert(desugarSets != null);
   assert(enableTripleShift != null);
   assert(errorOnUnevaluatedConstant != null);
   final ConstantsTransformer constantsTransformer = new ConstantsTransformer(
       backend,
       environmentDefines,
       evaluateAnnotations,
-      desugarSets,
       enableTripleShift,
       errorOnUnevaluatedConstant,
       typeEnvironment,
@@ -143,18 +139,15 @@
     ErrorReporter errorReporter,
     EvaluationMode evaluationMode,
     {bool evaluateAnnotations: true,
-    bool desugarSets: false,
     bool enableTripleShift: false,
     bool errorOnUnevaluatedConstant: false}) {
   assert(evaluateAnnotations != null);
-  assert(desugarSets != null);
   assert(enableTripleShift != null);
   assert(errorOnUnevaluatedConstant != null);
   final ConstantsTransformer constantsTransformer = new ConstantsTransformer(
       backend,
       environmentDefines,
       evaluateAnnotations,
-      desugarSets,
       enableTripleShift,
       errorOnUnevaluatedConstant,
       typeEnvironment,
@@ -326,7 +319,6 @@
   StaticTypeContext _staticTypeContext;
 
   final bool evaluateAnnotations;
-  final bool desugarSets;
   final bool enableTripleShift;
   final bool errorOnUnevaluatedConstant;
 
@@ -334,7 +326,6 @@
       this.backend,
       Map<String, String> environmentDefines,
       this.evaluateAnnotations,
-      this.desugarSets,
       this.enableTripleShift,
       this.errorOnUnevaluatedConstant,
       this.typeEnvironment,
@@ -342,7 +333,6 @@
       EvaluationMode evaluationMode)
       : constantEvaluator = new ConstantEvaluator(
             backend, environmentDefines, typeEnvironment, errorReporter,
-            desugarSets: desugarSets,
             enableTripleShift: enableTripleShift,
             errorOnUnevaluatedConstant: errorOnUnevaluatedConstant,
             evaluationMode: evaluationMode);
@@ -759,9 +749,6 @@
   final ErrorReporter errorReporter;
   final EvaluationMode evaluationMode;
 
-  final bool desugarSets;
-  final Field unmodifiableSetMap;
-
   final bool enableTripleShift;
 
   final bool Function(DartType) isInstantiated =
@@ -796,19 +783,14 @@
 
   ConstantEvaluator(this.backend, this.environmentDefines, this.typeEnvironment,
       this.errorReporter,
-      {this.desugarSets = false,
-      this.enableTripleShift = false,
+      {this.enableTripleShift = false,
       this.errorOnUnevaluatedConstant = false,
       this.evaluationMode: EvaluationMode.weak})
       : numberSemantics = backend.numberSemantics,
         coreTypes = typeEnvironment.coreTypes,
         canonicalizationCache = <Constant, Constant>{},
         nodeCache = <Node, Constant>{},
-        env = new EvaluationEnvironment(),
-        unmodifiableSetMap = desugarSets
-            ? typeEnvironment.coreTypes.index
-                .getMember('dart:collection', '_UnmodifiableSet', '_map')
-            : null {
+        env = new EvaluationEnvironment() {
     if (environmentDefines == null && !backend.supportsUnevaluatedConstants) {
       throw new ArgumentError(
           "No 'environmentDefines' passed to the constant evaluator but the "
diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
index de75625..5f55924 100644
--- a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
@@ -1247,7 +1247,6 @@
         new KernelConstantErrorReporter(loader),
         evaluationMode,
         evaluateAnnotations: true,
-        desugarSets: !backendTarget.supportsSetLiterals,
         enableTripleShift:
             isExperimentEnabledGlobally(ExperimentalFlag.tripleShift),
         errorOnUnevaluatedConstant: errorOnUnevaluatedConstant);
@@ -1297,7 +1296,6 @@
         new KernelConstantErrorReporter(loader),
         evaluationMode,
         evaluateAnnotations: true,
-        desugarSets: !backendTarget.supportsSetLiterals,
         enableTripleShift:
             isExperimentEnabledGlobally(ExperimentalFlag.tripleShift),
         errorOnUnevaluatedConstant: errorOnUnevaluatedConstant);
diff --git a/pkg/front_end/test/constant_evaluator_benchmark.dart b/pkg/front_end/test/constant_evaluator_benchmark.dart
index c152aa2..f0e722c 100644
--- a/pkg/front_end/test/constant_evaluator_benchmark.dart
+++ b/pkg/front_end/test/constant_evaluator_benchmark.dart
@@ -99,7 +99,6 @@
             new SilentErrorReporter(),
             evaluationMode,
             evaluateAnnotations: true,
-            desugarSets: !target.backendTarget.supportsSetLiterals,
             enableTripleShift: target
                 .isExperimentEnabledGlobally(ExperimentalFlag.tripleShift),
             errorOnUnevaluatedConstant:
diff --git a/pkg/front_end/test/crashing_test_case_minimizer.dart b/pkg/front_end/test/crashing_test_case_minimizer.dart
index 8d0fcc6..ea8ac64 100644
--- a/pkg/front_end/test/crashing_test_case_minimizer.dart
+++ b/pkg/front_end/test/crashing_test_case_minimizer.dart
@@ -2,90 +2,11 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:async' show Future, StreamSubscription;
+import 'dart:convert' show jsonDecode;
 
-import 'dart:convert' show JsonEncoder, jsonDecode, utf8;
+import 'dart:io' show File;
 
-import 'dart:io' show BytesBuilder, File, stdin, stdout;
-import 'dart:math' show max;
-
-import 'dart:typed_data' show Uint8List;
-
-import 'package:_fe_analyzer_shared/src/parser/parser.dart' show Parser;
-
-import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
-    show ErrorToken, ScannerConfiguration, Token;
-
-import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token;
-
-import 'package:dev_compiler/src/kernel/target.dart' show DevCompilerTarget;
-
-import 'package:front_end/src/api_prototype/compiler_options.dart'
-    show CompilerOptions, DiagnosticMessage;
-
-import 'package:front_end/src/api_prototype/experimental_flags.dart'
-    show ExperimentalFlag;
-
-import 'package:front_end/src/api_prototype/file_system.dart'
-    show FileSystem, FileSystemEntity, FileSystemException;
-
-import 'package:front_end/src/base/processed_options.dart'
-    show ProcessedOptions;
-
-import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext;
-
-import 'package:front_end/src/fasta/incremental_compiler.dart'
-    show IncrementalCompiler;
-
-import 'package:front_end/src/fasta/kernel/utils.dart' show ByteSink;
-import 'package:front_end/src/fasta/util/direct_parser_ast.dart';
-import 'package:front_end/src/fasta/util/direct_parser_ast_helper.dart';
-
-import 'package:front_end/src/fasta/util/textual_outline.dart'
-    show textualOutline;
-
-import 'package:kernel/ast.dart' show Component;
-
-import 'package:kernel/binary/ast_to_binary.dart' show BinaryPrinter;
-
-import 'package:kernel/target/targets.dart' show Target, TargetFlags;
-import 'package:package_config/package_config.dart';
-
-import "package:vm/target/flutter.dart" show FlutterTarget;
-
-import "package:vm/target/vm.dart" show VmTarget;
-
-import 'incremental_load_from_dill_suite.dart' show getOptions;
-
-import 'parser_test_listener.dart' show ParserTestListener;
-
-import 'parser_suite.dart' as parser_suite;
-
-final FakeFileSystem fs = new FakeFileSystem();
-Uri mainUri;
-Uri platformUri;
-bool noPlatform = false;
-bool nnbd = false;
-bool experimentalInvalidation = false;
-bool serialize = false;
-bool widgetTransformation = false;
-List<Uri> invalidate = [];
-String targetString = "VM";
-String expectedCrashLine;
-bool oldBlockDelete = false;
-bool lineDelete = false;
-bool byteDelete = false;
-bool askAboutRedirectCrashTarget = false;
-int stackTraceMatches = 1;
-Set<String> askedAboutRedirect = {};
-bool _quit = false;
-bool skip = false;
-
-Future<bool> shouldQuit() async {
-  // allow some time for stdin.listen to process data.
-  await new Future.delayed(new Duration(milliseconds: 5));
-  return _quit;
-}
+import 'crashing_test_case_minimizer_impl.dart';
 
 // TODO(jensj): Option to automatically find and search for _all_ crashes that
 // it uncovers --- i.e. it currently has an option to ask if we want to search
@@ -93,1727 +14,94 @@
 // for everything it sees. One can possibly just make a copy of the state of
 // the file system and save that for later...
 
+// TODO(jensj): Add asserts or similar where - after each rewrite - we run the
+// parser on it and verifies that no syntax errors have been introduced.
+
 main(List<String> arguments) async {
   String filename;
-  Uri loadFsJson;
+  Uri loadJson;
   for (String arg in arguments) {
-    if (arg.startsWith("--")) {
-      if (arg == "--nnbd") {
-        nnbd = true;
-      } else if (arg == "--experimental-invalidation") {
-        experimentalInvalidation = true;
-      } else if (arg == "--serialize") {
-        serialize = true;
-      } else if (arg.startsWith("--fsJson=")) {
-        String jsJson = arg.substring("--fsJson=".length);
-        loadFsJson = Uri.base.resolve(jsJson);
-      } else if (arg.startsWith("--platform=")) {
-        String platform = arg.substring("--platform=".length);
-        platformUri = Uri.base.resolve(platform);
-      } else if (arg == "--no-platform") {
-        noPlatform = true;
-      } else if (arg.startsWith("--invalidate=")) {
-        for (String s in arg.substring("--invalidate=".length).split(",")) {
-          invalidate.add(Uri.base.resolve(s));
-        }
-      } else if (arg.startsWith("--widgetTransformation")) {
-        widgetTransformation = true;
-      } else if (arg.startsWith("--target=VM")) {
-        targetString = "VM";
-      } else if (arg.startsWith("--target=flutter")) {
-        targetString = "flutter";
-      } else if (arg.startsWith("--target=ddc")) {
-        targetString = "ddc";
-      } else if (arg == "--oldBlockDelete") {
-        oldBlockDelete = true;
-      } else if (arg == "--lineDelete") {
-        lineDelete = true;
-      } else if (arg == "--byteDelete") {
-        byteDelete = true;
-      } else if (arg == "--ask-redirect-target") {
-        askAboutRedirectCrashTarget = true;
-      } else if (arg.startsWith("--stack-matches=")) {
-        String stackMatches = arg.substring("--stack-matches=".length);
-        stackTraceMatches = int.parse(stackMatches);
-      } else {
-        throw "Unknown option $arg";
-      }
-    } else if (filename != null) {
-      throw "Already got '$filename', '$arg' is also a filename; "
-          "can only get one";
-    } else {
-      filename = arg;
-    }
-  }
-  if (noPlatform) {
-    int i = 0;
-    while (platformUri == null || new File.fromUri(platformUri).existsSync()) {
-      platformUri = Uri.base.resolve("nonexisting_$i");
-      i++;
-    }
-  } else {
-    if (platformUri == null) {
-      throw "No platform given. Use --platform=/path/to/platform.dill";
-    }
-    if (!new File.fromUri(platformUri).existsSync()) {
-      throw "The platform file '$platformUri' doesn't exist";
-    }
-  }
-  if (filename == null) {
-    throw "Need file to operate on";
-  }
-  File file = new File(filename);
-  if (!file.existsSync()) throw "File $filename doesn't exist.";
-  mainUri = file.absolute.uri;
-
-  try {
-    await tryToMinimize(loadFsJson);
-  } catch (e) {
-    print("\n\n\nABOUT TO CRASH. DUMPING FS.");
-    dumpFsToJson();
-    print("\n\n\nABOUT TO CRASH. FS DUMPED.");
-    rethrow;
-  }
-}
-
-Future tryToMinimize(Uri loadFsJson) async {
-  // Set main to be basically empty up front.
-  fs.data[mainUri] = utf8.encode("main() {}");
-  Component initialComponent = await getInitialComponent();
-  print("Compiled initially (without data)");
-  // Remove fake cache.
-  fs.data.remove(mainUri);
-
-  if (loadFsJson != null) {
-    File f = new File.fromUri(loadFsJson);
-    fs.initializeFromJson((jsonDecode(f.readAsStringSync())));
-  }
-
-  // First assure it actually crash on the input.
-  if (!await crashesOnCompile(initialComponent)) {
-    throw "Input doesn't crash the compiler.";
-  }
-  print("Step #1: We did crash on the input!");
-
-  // All file should now be cached.
-  fs._redirectAndRecord = false;
-
-  try {
-    stdin.echoMode = false;
-    stdin.lineMode = false;
-  } catch (e) {
-    print("error setting settings on stdin");
-  }
-  StreamSubscription<List<int>> stdinSubscription =
-      stdin.listen((List<int> event) {
-    if (event.length == 1 && event.single == "q".codeUnits.single) {
-      print("\n\nGot told to quit!\n\n");
-      _quit = true;
-    } else if (event.length == 1 && event.single == "s".codeUnits.single) {
-      print("\n\nGot told to skip!\n\n");
-      skip = true;
-    } else if (event.length == 1 && event.single == "i".codeUnits.single) {
-      print("\n\n--- STATUS INFORMATION START ---\n\n");
-      int totalFiles = 0;
-      int emptyFiles = 0;
-      int combinedSize = 0;
-      for (Uri uri in fs.data.keys) {
-        final Uint8List originalBytes = fs.data[uri];
-        if (originalBytes == null) continue;
-        totalFiles++;
-        if (originalBytes.isEmpty) emptyFiles++;
-        combinedSize += originalBytes.length;
-      }
-      print("Total files left: $totalFiles.");
-      print("Of which empty: $emptyFiles.");
-      print("Combined size left: $combinedSize bytes.");
-      print("\n\n--- STATUS INFORMATION END ---\n\n");
-      skip = true;
-    } else {
-      print("\n\nGot stdin input: $event\n\n");
-    }
-  });
-
-  // For all dart files: Parse them as set their source as the parsed source
-  // to "get around" any encoding issues when printing later.
-  Map<Uri, Uint8List> copy = new Map.from(fs.data);
-  for (Uri uri in fs.data.keys) {
-    if (await shouldQuit()) break;
-    String uriString = uri.toString();
-    if (uriString.endsWith(".json") ||
-        uriString.endsWith(".packages") ||
-        uriString.endsWith(".dill") ||
-        fs.data[uri] == null ||
-        fs.data[uri].isEmpty) {
-      // skip
-    } else {
-      try {
-        String parsedString = getFileAsStringContent(fs.data[uri], nnbd);
-        fs.data[uri] = utf8.encode(parsedString);
-      } catch (e) {
-        // crash in scanner/parser --- keep original file. This crash might
-        // be what we're looking for!
-      }
-    }
-  }
-  if (!await crashesOnCompile(initialComponent)) {
-    // Now - for whatever reason - we didn't crash. Restore.
-    fs.data.clear();
-    fs.data.addAll(copy);
-  }
-
-  // Operate on one file at a time: Try to delete all content in file.
-  List<Uri> uris = new List<Uri>.from(fs.data.keys);
-
-  // TODO(jensj): Can we "thread" this?
-  bool changedSome = true;
-  while (changedSome) {
-    if (await shouldQuit()) break;
-    while (changedSome) {
-      if (await shouldQuit()) break;
-      changedSome = false;
-      for (int i = 0; i < uris.length; i++) {
-        if (await shouldQuit()) break;
-        Uri uri = uris[i];
-        if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
-        print("About to work on file $i of ${uris.length}");
-        await deleteContent(uris, i, false, initialComponent);
-        if (fs.data[uri] == null || fs.data[uri].isEmpty) changedSome = true;
-      }
-    }
-
-    // Try to delete empty files.
-    bool changedSome2 = true;
-    while (changedSome2) {
-      if (await shouldQuit()) break;
-      changedSome2 = false;
-      for (int i = 0; i < uris.length; i++) {
-        if (await shouldQuit()) break;
-        Uri uri = uris[i];
-        if (fs.data[uri] == null || fs.data[uri].isNotEmpty) continue;
-        print("About to work on file $i of ${uris.length}");
-        await deleteContent(uris, i, false, initialComponent, deleteFile: true);
-        if (fs.data[uri] == null) {
-          changedSome = true;
-          changedSome2 = true;
-        }
-      }
-    }
-
-    int left = 0;
-    for (Uri uri in uris) {
-      if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
-      left++;
-    }
-    print("There's now $left files of ${fs.data.length} files left");
-
-    // Operate on one file at a time.
-    for (Uri uri in fs.data.keys) {
-      if (await shouldQuit()) break;
-      if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
-
-      print("Now working on $uri");
-
-      int prevLength = fs.data[uri].length;
-
-      await deleteBlocks(uri, initialComponent);
-      await deleteEmptyLines(uri, initialComponent);
-
-      if (oldBlockDelete) {
-        // Try to delete blocks.
-        await deleteBlocksOld(uri, initialComponent);
-      }
-
-      if (lineDelete) {
-        // Try to delete lines.
-        await deleteLines(uri, initialComponent);
-      }
-
-      print("We're now at ${fs.data[uri].length} bytes for $uri "
-          "(was $prevLength).");
-      if (prevLength != fs.data[uri].length) changedSome = true;
-      if (fs.data[uri].isEmpty) continue;
-
-      if (byteDelete) {
-        // Now try to delete 'arbitrarily' (for any given start offset do an
-        // exponential binary search).
-        int prevLength = fs.data[uri].length;
-        while (true) {
-          if (await shouldQuit()) break;
-          await binarySearchDeleteData(uri, initialComponent);
-
-          if (fs.data[uri].length == prevLength) {
-            // No progress.
-            break;
-          } else {
-            print("We're now at ${fs.data[uri].length} bytes");
-            prevLength = fs.data[uri].length;
-            changedSome = true;
-          }
-        }
-      }
-    }
-    for (Uri uri in fs.data.keys) {
-      if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
-      if (await shouldQuit()) break;
-      if (await attemptInline(uri, initialComponent)) {
-        changedSome = true;
-      }
-    }
-  }
-
-  if (await shouldQuit()) {
-    print("\n\nASKED TO QUIT\n\n");
-  } else {
-    print("\n\nDONE\n\n");
-  }
-
-  Uri jsonFsOut = dumpFsToJson();
-
-  await stdinSubscription.cancel();
-
-  if (!await shouldQuit()) {
-    // Test converting to incremental compiler yaml test.
-    outputIncrementalCompilerYamlTest();
-    print("\n\n\n");
-
-    for (Uri uri in uris) {
-      if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
-      print("Uri $uri has this content:");
-
-      try {
-        String utfDecoded = utf8.decode(fs.data[uri], allowMalformed: true);
-        print(utfDecoded);
-      } catch (e) {
-        print(fs.data[uri]);
-        print("(which crashes when trying to decode as utf8)");
-      }
-      print("\n\n====================\n\n");
-    }
-
-    print("Wrote json file system to $jsonFsOut");
-  }
-}
-
-Uri dumpFsToJson() {
-  JsonEncoder jsonEncoder = new JsonEncoder.withIndent("  ");
-  String jsonFs = jsonEncoder.convert(fs);
-  int i = 0;
-  Uri jsonFsOut;
-  while (jsonFsOut == null || new File.fromUri(jsonFsOut).existsSync()) {
-    jsonFsOut = Uri.base.resolve("crash_minimizer_result_$i");
-    i++;
-  }
-  new File.fromUri(jsonFsOut).writeAsStringSync(jsonFs);
-  print("Wrote json file system to $jsonFsOut");
-  return jsonFsOut;
-}
-
-/// Attempts to inline small files in other files.
-/// Returns true if anything was changed, i.e. if at least one inlining was a
-/// success.
-Future<bool> attemptInline(Uri uri, Component initialComponent) async {
-  // Don't attempt to inline the main uri --- that's our entry!
-  if (uri == mainUri) return false;
-
-  Uint8List inlineData = fs.data[uri];
-  bool hasMultipleLines = false;
-  for (int i = 0; i < inlineData.length; i++) {
-    if (inlineData[i] == $LF) {
-      hasMultipleLines = true;
+    if (arg.startsWith("--json=")) {
+      String json = arg.substring("--json=".length);
+      loadJson = Uri.base.resolve(json);
       break;
     }
   }
-  // TODO(jensj): Maybe inline slightly bigger files too?
-  if (hasMultipleLines) {
-    return false;
-  }
+  TestMinimizerSettings settings = new TestMinimizerSettings();
 
-  Uri inlinableUri = uri;
-
-  int compileTry = 0;
-  bool changed = false;
-
-  for (Uri uri in fs.data.keys) {
-    final Uint8List originalBytes = fs.data[uri];
-    if (originalBytes == null || originalBytes.isEmpty) continue;
-    DirectParserASTContentCompilationUnitEnd ast = getAST(originalBytes,
-        includeBody: false,
-        includeComments: false,
-        enableExtensionMethods: true,
-        enableNonNullable: nnbd);
-    // Find all imports/exports of this file (if any).
-    // If finding any:
-    // * remove all of them, then
-    // * find the end of the last import/export, and
-    // * insert the content of the file there.
-    // * if that *doesn't* work and we've inserted an export,
-    //   try converting that to an import instead.
-    List<Replacement> replacements = [];
-    for (DirectParserASTContentImportEnd import in ast.getImports()) {
-      Token importUriToken = import.importKeyword.next;
-      Uri importUri = _getUri(importUriToken, uri);
-      if (inlinableUri == importUri) {
-        replacements.add(new Replacement(
-            import.importKeyword.offset - 1, import.semicolon.offset + 1));
-      }
-    }
-    for (DirectParserASTContentExportEnd export in ast.getExports()) {
-      Token exportUriToken = export.exportKeyword.next;
-      Uri exportUri = _getUri(exportUriToken, uri);
-      if (inlinableUri == exportUri) {
-        replacements.add(new Replacement(
-            export.exportKeyword.offset - 1, export.semicolon.offset + 1));
-      }
-    }
-    if (replacements.isEmpty) continue;
-
-    // Step 1: Remove all imports/exports of this file.
-    Uint8List candidate = _replaceRange(replacements, originalBytes);
-
-    // Step 2: Find the last import/export.
-    int offsetOfLast = 0;
-    ast = getAST(candidate,
-        includeBody: false,
-        includeComments: false,
-        enableExtensionMethods: true,
-        enableNonNullable: nnbd);
-    for (DirectParserASTContentImportEnd import in ast.getImports()) {
-      offsetOfLast = max(offsetOfLast, import.semicolon.offset + 1);
-    }
-    for (DirectParserASTContentExportEnd export in ast.getExports()) {
-      offsetOfLast = max(offsetOfLast, export.semicolon.offset + 1);
-    }
-
-    // Step 3: Insert the content of the file there. Note, though,
-    // that any imports/exports in _that_ file should be changed to be valid
-    // in regards to the new placement.
-    BytesBuilder builder = new BytesBuilder();
-    for (int i = 0; i < offsetOfLast; i++) {
-      builder.addByte(candidate[i]);
-    }
-    builder.addByte($LF);
-    builder.add(_rewriteImportsExportsToUri(inlineData, uri, inlinableUri));
-    builder.addByte($LF);
-    for (int i = offsetOfLast; i < candidate.length; i++) {
-      builder.addByte(candidate[i]);
-    }
-    candidate = builder.takeBytes();
-
-    // Step 4: Try it out.
-    if (await shouldQuit()) break;
-    if (skip) {
-      skip = false;
-      break;
-    }
-    stdout.write(".");
-    compileTry++;
-    if (compileTry % 50 == 0) {
-      stdout.write("(at $compileTry)\n");
-    }
-    fs.data[uri] = candidate;
-    if (await crashesOnCompile(initialComponent)) {
-      print("Could inline $inlinableUri into $uri.");
-      changed = true;
-      // File was already updated.
-    } else {
-      // Couldn't replace that.
-      // Insert the original again.
-      fs.data[uri] = originalBytes;
-
-      // If we've inlined an export, try changing that to an import.
-      builder = new BytesBuilder();
-      for (int i = 0; i < offsetOfLast; i++) {
-        builder.addByte(candidate[i]);
-      }
-      // TODO(jensj): Only try compile again, if export was actually converted
-      // to import.
-      builder.addByte($LF);
-      builder.add(_rewriteImportsExportsToUri(inlineData, uri, inlinableUri,
-          convertExportToImport: true));
-      builder.addByte($LF);
-      for (int i = offsetOfLast; i < candidate.length; i++) {
-        builder.addByte(candidate[i]);
-      }
-      candidate = builder.takeBytes();
-
-      // Step 4: Try it out.
-      if (await shouldQuit()) break;
-      if (skip) {
-        skip = false;
-        break;
-      }
-      stdout.write(".");
-      compileTry++;
-      if (compileTry % 50 == 0) {
-        stdout.write("(at $compileTry)\n");
-      }
-      fs.data[uri] = candidate;
-      if (await crashesOnCompile(initialComponent)) {
-        print("Could inline $inlinableUri into $uri "
-            "(by converting export to import).");
-        changed = true;
-        // File was already updated.
-      } else {
-        // Couldn't replace that.
-        // Insert the original again.
-        fs.data[uri] = originalBytes;
-      }
-    }
-  }
-
-  return changed;
-}
-
-Uint8List _rewriteImportsExportsToUri(Uint8List oldData, Uri newUri, Uri oldUri,
-    {bool convertExportToImport: false}) {
-  DirectParserASTContentCompilationUnitEnd ast = getAST(oldData,
-      includeBody: false,
-      includeComments: false,
-      enableExtensionMethods: true,
-      enableNonNullable: nnbd);
-  List<Replacement> replacements = [];
-  for (DirectParserASTContentImportEnd import in ast.getImports()) {
-    _rewriteImportsExportsToUriInternal(
-        import.importKeyword.next, oldUri, replacements, newUri);
-  }
-  for (DirectParserASTContentExportEnd export in ast.getExports()) {
-    if (convertExportToImport) {
-      replacements.add(new Replacement(
-        export.exportKeyword.offset - 1,
-        export.exportKeyword.offset + export.exportKeyword.length,
-        nullOrReplacement: utf8.encode('import'),
-      ));
-    }
-    _rewriteImportsExportsToUriInternal(
-        export.exportKeyword.next, oldUri, replacements, newUri);
-  }
-  if (replacements.isNotEmpty) {
-    Uint8List candidate = _replaceRange(replacements, oldData);
-    return candidate;
-  }
-  return oldData;
-}
-
-void _rewriteImportsExportsToUriInternal(
-    Token uriToken, Uri oldUri, List<Replacement> replacements, Uri newUri) {
-  Uri tokenUri = _getUri(uriToken, oldUri, resolvePackage: false);
-  if (tokenUri.scheme == "package" || tokenUri.scheme == "dart") return;
-  Uri asPackageUri = _getImportUri(tokenUri);
-  if (asPackageUri.scheme == "package") {
-    // Just replace with this package uri.
-    replacements.add(new Replacement(
-      uriToken.offset - 1,
-      uriToken.offset + uriToken.length,
-      nullOrReplacement: utf8.encode('"${asPackageUri.toString()}"'),
-    ));
+  if (loadJson != null) {
+    File f = new File.fromUri(loadJson);
+    settings.initializeFromJson((jsonDecode(f.readAsStringSync())));
   } else {
-    // TODO(jensj): Rewrite relative path to be correct.
-    throw "Rewrite $oldUri importing/exporting $tokenUri as $uriToken "
-        "for $newUri (notice $asPackageUri)";
-  }
-}
-
-Uri _getUri(Token uriToken, Uri uri, {bool resolvePackage: true}) {
-  String uriString = uriToken.lexeme;
-  uriString = uriString.substring(1, uriString.length - 1);
-  Uri uriTokenUri = uri.resolve(uriString);
-  if (resolvePackage && uriTokenUri.scheme == "package") {
-    Package package = _latestIncrementalCompiler
-        .currentPackagesMap[uriTokenUri.pathSegments.first];
-    uriTokenUri = package.packageUriRoot
-        .resolve(uriTokenUri.pathSegments.skip(1).join("/"));
-  }
-  return uriTokenUri;
-}
-
-Uri _getImportUri(Uri uri) {
-  return _latestIncrementalCompiler.userCode
-      .getEntryPointUri(uri, issueProblem: false);
-}
-
-void outputIncrementalCompilerYamlTest() {
-  int dartFiles = 0;
-  for (MapEntry<Uri, Uint8List> entry in fs.data.entries) {
-    if (entry.key.pathSegments.last.endsWith(".dart")) {
-      if (entry.value != null) dartFiles++;
-    }
-  }
-
-  print("------ Reproduction as semi-done incremental yaml test file ------");
-
-  // TODO(jensj): don't use full uris.
-  print("""
-# 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.md file.
-
-# Reproduce a crash.
-
-type: newworld""");
-  if (widgetTransformation) {
-    print("trackWidgetCreation: true");
-    print("target: DDC # basically needed for widget creation to be run");
-  }
-  print("""
-worlds:
-  - entry: $mainUri""");
-  if (experimentalInvalidation) {
-    print("    experiments: alternative-invalidation-strategy");
-  }
-  print("    sources:");
-  for (MapEntry<Uri, Uint8List> entry in fs.data.entries) {
-    if (entry.value == null) continue;
-    print("      ${entry.key}: |");
-    String string = utf8.decode(entry.value);
-    List<String> lines = string.split("\n");
-    for (String line in lines) {
-      print("        $line");
-    }
-  }
-  print("    expectedLibraryCount: $dartFiles "
-      "# with parts this is not right");
-  print("");
-
-  for (Uri uri in invalidate) {
-    print("  - entry: $mainUri");
-    if (experimentalInvalidation) {
-      print("    experiments: alternative-invalidation-strategy");
-    }
-    print("    worldType: updated");
-    print("    expectInitializeFromDill: false # or true?");
-    print("    invalidate:");
-    print("      - $uri");
-    print("    expectedLibraryCount: $dartFiles "
-        "# with parts this is not right");
-    print("    expectsRebuildBodiesOnly: true # or false?");
-    print("");
-  }
-
-  print("------------------------------------------------------------------");
-}
-
-Uint8List sublist(Uint8List data, int start, int end) {
-  Uint8List result = new Uint8List(end - start);
-  result.setRange(0, result.length, data, start);
-  return result;
-}
-
-String dataToText(Uint8List data) {
-  StringBuffer sb = new StringBuffer();
-  String comma = "[";
-  for (int i = 0; i < data.length; i++) {
-    sb.write(comma);
-    sb.write(data[i]);
-    comma = ", ";
-    if (i > 100) break;
-  }
-  if (data.length > 100) {
-    sb.write("...");
-  }
-  sb.write("]");
-  return sb.toString();
-}
-
-void binarySearchDeleteData(Uri uri, Component initialComponent) async {
-  Uint8List latestCrashData = fs.data[uri];
-  int offset = 0;
-  while (offset < latestCrashData.length) {
-    print("Working at offset $offset of ${latestCrashData.length}");
-    BytesBuilder builder = new BytesBuilder();
-    builder.add(sublist(latestCrashData, 0, offset));
-    builder.add(sublist(latestCrashData, offset + 1, latestCrashData.length));
-    Uint8List candidate = builder.takeBytes();
-    fs.data[uri] = candidate;
-    if (!await crashesOnCompile(initialComponent)) {
-      // Deleting 1 char didn't crash; don't try to delete anymore starting
-      // here.
-      offset++;
-      continue;
-    }
-
-    // Find how long we can go.
-    int crashingAt = 1;
-    int noLongerCrashingAt;
-    while (true) {
-      int deleteChars = 2 * crashingAt;
-      if (offset + deleteChars > latestCrashData.length) {
-        deleteChars = latestCrashData.length - offset;
-      }
-      builder = new BytesBuilder();
-      builder.add(sublist(latestCrashData, 0, offset));
-      builder.add(sublist(
-          latestCrashData, offset + deleteChars, latestCrashData.length));
-      candidate = builder.takeBytes();
-      fs.data[uri] = candidate;
-      if (!await crashesOnCompile(initialComponent)) {
-        noLongerCrashingAt = deleteChars;
-        break;
-      }
-      crashingAt = deleteChars;
-      if (crashingAt + offset == latestCrashData.length) break;
-    }
-
-    if (noLongerCrashingAt == null) {
-      // We can delete the rest.
-      latestCrashData = candidate;
-      continue;
-    }
-
-    // Binary search between [crashingAt] and [noLongerCrashingAt].
-    while (crashingAt < noLongerCrashingAt) {
-      int mid = noLongerCrashingAt -
-          ((noLongerCrashingAt - crashingAt) >> 1); // Get middle, rounding up.
-      builder = new BytesBuilder();
-      builder.add(sublist(latestCrashData, 0, offset));
-      builder
-          .add(sublist(latestCrashData, offset + mid, latestCrashData.length));
-      candidate = builder.takeBytes();
-      fs.data[uri] = candidate;
-      if (await crashesOnCompile(initialComponent)) {
-        crashingAt = mid;
-      } else {
-        // [noLongerCrashingAt] might actually crash now.
-        noLongerCrashingAt = mid - 1;
-      }
-    }
-
-    // This is basically an assert.
-    builder = new BytesBuilder();
-    builder.add(sublist(latestCrashData, 0, offset));
-    builder.add(
-        sublist(latestCrashData, offset + crashingAt, latestCrashData.length));
-    candidate = builder.takeBytes();
-    fs.data[uri] = candidate;
-    if (!await crashesOnCompile(initialComponent)) {
-      throw "Error in binary search.";
-    }
-    latestCrashData = candidate;
-  }
-
-  fs.data[uri] = latestCrashData;
-}
-
-void _tryToRemoveUnreferencedFileContent(Component initialComponent,
-    {bool deleteFile: false}) async {
-  // Check if there now are any unused files.
-  if (_latestComponent == null) return;
-  Set<Uri> neededUris = _latestComponent.uriToSource.keys.toSet();
-  Map<Uri, Uint8List> copy = new Map.from(fs.data);
-  bool removedSome = false;
-  if (await shouldQuit()) return;
-  for (MapEntry<Uri, Uint8List> entry in fs.data.entries) {
-    if (entry.value == null || entry.value.isEmpty) continue;
-    if (!entry.key.toString().endsWith(".dart")) continue;
-    if (!neededUris.contains(entry.key) && fs.data[entry.key].length != 0) {
-      if (deleteFile) {
-        fs.data[entry.key] = null;
-      } else {
-        fs.data[entry.key] = new Uint8List(0);
-      }
-      print(" => Can probably also delete ${entry.key}");
-      removedSome = true;
-    }
-  }
-  if (removedSome) {
-    if (await crashesOnCompile(initialComponent)) {
-      print(" => Yes; Could remove those too!");
-    } else {
-      print(" => No; Couldn't remove those too!");
-      fs.data.clear();
-      fs.data.addAll(copy);
-    }
-  }
-}
-
-void deleteContent(
-    List<Uri> uris, int uriIndex, bool limitTo1, Component initialComponent,
-    {bool deleteFile: false}) async {
-  String extraMessageText = "all content of ";
-  if (deleteFile) extraMessageText = "";
-
-  if (!limitTo1) {
-    if (await shouldQuit()) return;
-    Map<Uri, Uint8List> copy = new Map.from(fs.data);
-    // Try to remove content of i and the next 9 (10 files in total).
-    for (int j = uriIndex; j < uriIndex + 10 && j < uris.length; j++) {
-      Uri uri = uris[j];
-      if (deleteFile) {
-        fs.data[uri] = null;
-      } else {
-        fs.data[uri] = new Uint8List(0);
-      }
-    }
-    if (!await crashesOnCompile(initialComponent)) {
-      // Couldn't delete all 10 files. Restore and try the single one.
-      fs.data.clear();
-      fs.data.addAll(copy);
-    } else {
-      for (int j = uriIndex; j < uriIndex + 10 && j < uris.length; j++) {
-        Uri uri = uris[j];
-        print("Can delete ${extraMessageText}file $uri");
-      }
-      await _tryToRemoveUnreferencedFileContent(initialComponent,
-          deleteFile: deleteFile);
-      return;
-    }
-  }
-
-  if (await shouldQuit()) return;
-  Uri uri = uris[uriIndex];
-  Uint8List data = fs.data[uri];
-  if (deleteFile) {
-    fs.data[uri] = null;
-  } else {
-    fs.data[uri] = new Uint8List(0);
-  }
-  if (!await crashesOnCompile(initialComponent)) {
-    print("Can't delete ${extraMessageText}file $uri -- keeping it (for now)");
-    fs.data[uri] = data;
-
-    // For dart files we can't truncate completely try to "outline" them
-    // instead.
-    if (uri.toString().endsWith(".dart")) {
-      String textualOutlined =
-          textualOutline(data)?.replaceAll(RegExp(r'\n+'), "\n");
-
-      bool outlined = false;
-      if (textualOutlined != null) {
-        Uint8List candidate = utf8.encode(textualOutlined);
-        if (candidate.length != fs.data[uri].length) {
-          if (await shouldQuit()) return;
-          fs.data[uri] = candidate;
-          if (!await crashesOnCompile(initialComponent)) {
-            print("Can't outline the file $uri -- keeping it (for now)");
-            fs.data[uri] = data;
-          } else {
-            outlined = true;
-            print(
-                "Can outline the file $uri (now ${fs.data[uri].length} bytes)");
+    for (String arg in arguments) {
+      if (arg.startsWith("--")) {
+        if (arg == "--experimental-invalidation") {
+          settings.experimentalInvalidation = true;
+        } else if (arg == "--serialize") {
+          settings.serialize = true;
+        } else if (arg.startsWith("--platform=")) {
+          String platform = arg.substring("--platform=".length);
+          settings.platformUri = Uri.base.resolve(platform);
+        } else if (arg == "--no-platform") {
+          settings.noPlatform = true;
+        } else if (arg.startsWith("--invalidate=")) {
+          for (String s in arg.substring("--invalidate=".length).split(",")) {
+            settings.invalidate.add(Uri.base.resolve(s));
           }
-        }
-      }
-      if (!outlined) {
-        // We can probably at least remove all comments then...
-        try {
-          List<String> strings = utf8.decode(fs.data[uri]).split("\n");
-          List<String> stringsLeft = [];
-          for (String string in strings) {
-            if (!string.trim().startsWith("//")) stringsLeft.add(string);
-          }
-
-          Uint8List candidate = utf8.encode(stringsLeft.join("\n"));
-          if (candidate.length != fs.data[uri].length) {
-            if (await shouldQuit()) return;
-            fs.data[uri] = candidate;
-            if (!await crashesOnCompile(initialComponent)) {
-              print("Can't remove comments for file $uri -- "
-                  "keeping it (for now)");
-              fs.data[uri] = data;
-            } else {
-              print("Removed comments for the file $uri");
-            }
-          }
-        } catch (e) {
-          // crash in scanner/parser --- keep original file. This crash might
-          // be what we're looking for!
-        }
-      }
-    }
-  } else {
-    print("Can delete ${extraMessageText}file $uri");
-    await _tryToRemoveUnreferencedFileContent(initialComponent);
-  }
-}
-
-void deleteBlocksOld(Uri uri, Component initialComponent) async {
-  if (uri.toString().endsWith(".json")) {
-    // Try to find annoying
-    //
-    //    },
-    //    {
-    //    }
-    //
-    // part of json and remove it.
-    Uint8List data = fs.data[uri];
-    String string = utf8.decode(data);
-    List<String> lines = string.split("\n");
-    for (int i = 0; i < lines.length - 2; i++) {
-      if (lines[i].trim() == "}," &&
-          lines[i + 1].trim() == "{" &&
-          lines[i + 2].trim() == "}") {
-        // This is the pattern we wanted to find. Remove it.
-        lines.removeRange(i, i + 2);
-        i--;
-      }
-    }
-    string = lines.join("\n");
-    fs.data[uri] = utf8.encode(string);
-    if (!await crashesOnCompile(initialComponent)) {
-      // For some reason that didn't work.
-      fs.data[uri] = data;
-    }
-  }
-  if (!uri.toString().endsWith(".dart")) return;
-
-  Uint8List data = fs.data[uri];
-  Uint8List latestCrashData = data;
-
-  List<int> lineStarts = <int>[];
-
-  Token firstToken = parser_suite.scanRawBytes(data,
-      nnbd ? scannerConfiguration : scannerConfigurationNonNNBD, lineStarts);
-
-  if (firstToken == null) {
-    print("Got null token from scanner for $uri");
-    return;
-  }
-
-  int compileTry = 0;
-  Token token = firstToken;
-  while (token is ErrorToken) {
-    token = token.next;
-  }
-  List<Replacement> replacements = [];
-  while (token != null && !token.isEof) {
-    bool tryCompile = false;
-    Token skipToToken = token;
-    // Skip very small blocks (e.g. "{}" or "{\n}");
-    if (token.endGroup != null && token.offset + 3 < token.endGroup.offset) {
-      replacements.add(new Replacement(token.offset, token.endGroup.offset));
-      tryCompile = true;
-      skipToToken = token.endGroup;
-    } else if (token.lexeme == "@") {
-      if (token.next.next.endGroup != null) {
-        int end = token.next.next.endGroup.offset;
-        skipToToken = token.next.next.endGroup;
-        replacements.add(new Replacement(token.offset - 1, end + 1));
-        tryCompile = true;
-      }
-    } else if (token.lexeme == "assert") {
-      if (token.next.endGroup != null) {
-        int end = token.next.endGroup.offset;
-        skipToToken = token.next.endGroup;
-        if (token.next.endGroup.next.lexeme == ",") {
-          end = token.next.endGroup.next.offset;
-          skipToToken = token.next.endGroup.next;
-        }
-        // +/- 1 to not include the start and the end character.
-        replacements.add(new Replacement(token.offset - 1, end + 1));
-        tryCompile = true;
-      }
-    } else if ((token.lexeme == "abstract" && token.next.lexeme == "class") ||
-        token.lexeme == "class" ||
-        token.lexeme == "enum" ||
-        token.lexeme == "mixin" ||
-        token.lexeme == "static" ||
-        token.next.lexeme == "get" ||
-        token.next.lexeme == "set" ||
-        token.next.next.lexeme == "(" ||
-        (token.next.lexeme == "<" &&
-            token.next.endGroup != null &&
-            token.next.endGroup.next.next.lexeme == "(")) {
-      // Try to find and remove the entire class/enum/mixin/
-      // static procedure/getter/setter/simple procedure.
-      Token bracket = token;
-      for (int i = 0; i < 20; i++) {
-        // Find "{", but only go a maximum of 20 tokens to do that.
-        bracket = bracket.next;
-        if (bracket.lexeme == "{" && bracket.endGroup != null) {
-          break;
-        } else if ((bracket.lexeme == "(" || bracket.lexeme == "<") &&
-            bracket.endGroup != null) {
-          bracket = bracket.endGroup;
-        }
-      }
-      if (bracket.lexeme == "{" && bracket.endGroup != null) {
-        int end = bracket.endGroup.offset;
-        skipToToken = bracket.endGroup;
-        // +/- 1 to not include the start and the end character.
-        replacements.add(new Replacement(token.offset - 1, end + 1));
-        tryCompile = true;
-      }
-    }
-
-    if (tryCompile) {
-      if (await shouldQuit()) break;
-      if (skip) {
-        skip = false;
-        break;
-      }
-      stdout.write(".");
-      compileTry++;
-      if (compileTry % 50 == 0) {
-        stdout.write("(at $compileTry)\n");
-      }
-      Uint8List candidate = _replaceRange(replacements, data);
-      fs.data[uri] = candidate;
-      if (await crashesOnCompile(initialComponent)) {
-        print("Found block from "
-            "${replacements.last.from} to "
-            "${replacements.last.to} "
-            "that can be removed.");
-        latestCrashData = candidate;
-        token = skipToToken;
-      } else {
-        // Couldn't delete that.
-        replacements.removeLast();
-      }
-    }
-    token = token.next;
-  }
-  fs.data[uri] = latestCrashData;
-}
-
-void deleteBlocks(final Uri uri, Component initialComponent) async {
-  if (uri.toString().endsWith(".json")) {
-    // Try to find annoying
-    //
-    //    },
-    //    {
-    //    }
-    //
-    // part of json and remove it.
-    Uint8List data = fs.data[uri];
-    String string = utf8.decode(data);
-    List<String> lines = string.split("\n");
-    for (int i = 0; i < lines.length - 2; i++) {
-      if (lines[i].trim() == "}," &&
-          lines[i + 1].trim() == "{" &&
-          lines[i + 2].trim() == "}") {
-        // This is the pattern we wanted to find. Remove it.
-        lines.removeRange(i, i + 2);
-        i--;
-      }
-    }
-    string = lines.join("\n");
-    Uint8List candidate = utf8.encode(string);
-    if (candidate.length != data.length) {
-      fs.data[uri] = candidate;
-      if (!await crashesOnCompile(initialComponent)) {
-        // For some reason that didn't work.
-        fs.data[uri] = data;
-      }
-    }
-
-    // Try to load json and remove blocks.
-    try {
-      Map json = jsonDecode(utf8.decode(data));
-      Map jsonModified = new Map.from(json);
-      List packages = json["packages"];
-      List packagesModified = new List.from(packages);
-      jsonModified["packages"] = packagesModified;
-      int i = 0;
-      print("Note there's ${packagesModified.length} packages in .json");
-      JsonEncoder jsonEncoder = new JsonEncoder.withIndent("  ");
-      while (i < packagesModified.length) {
-        var oldEntry = packagesModified.removeAt(i);
-        String jsonString = jsonEncoder.convert(jsonModified);
-        candidate = utf8.encode(jsonString);
-        Uint8List previous = fs.data[uri];
-        fs.data[uri] = candidate;
-        if (!await crashesOnCompile(initialComponent)) {
-          // Couldn't remove that part.
-          fs.data[uri] = previous;
-          packagesModified.insert(i, oldEntry);
-          i++;
+        } else if (arg.startsWith("--widgetTransformation")) {
+          settings.widgetTransformation = true;
+        } else if (arg.startsWith("--target=VM")) {
+          settings.targetString = "VM";
+        } else if (arg.startsWith("--target=flutter")) {
+          settings.targetString = "flutter";
+        } else if (arg.startsWith("--target=ddc")) {
+          settings.targetString = "ddc";
+        } else if (arg == "--oldBlockDelete") {
+          settings.oldBlockDelete = true;
+        } else if (arg == "--lineDelete") {
+          settings.lineDelete = true;
+        } else if (arg == "--byteDelete") {
+          settings.byteDelete = true;
+        } else if (arg == "--ask-redirect-target") {
+          settings.askAboutRedirectCrashTarget = true;
+        } else if (arg == "--auto-uncover-all-crashes") {
+          settings.autoUncoverAllCrashes = true;
+        } else if (arg.startsWith("--stack-matches=")) {
+          String stackMatches = arg.substring("--stack-matches=".length);
+          settings.stackTraceMatches = int.parse(stackMatches);
         } else {
-          print(
-              "Removed package from .json (${packagesModified.length} left).");
+          throw "Unknown option $arg";
         }
-      }
-    } catch (e) {
-      // Couldn't decode it, so don't try to do anything.
-    }
-    return;
-  }
-  if (!uri.toString().endsWith(".dart")) return;
-
-  Uint8List data = fs.data[uri];
-  DirectParserASTContentCompilationUnitEnd ast = getAST(data,
-      includeBody: true,
-      includeComments: false,
-      enableExtensionMethods: true,
-      enableNonNullable: nnbd);
-
-  CompilationHelperClass helper = new CompilationHelperClass(data);
-
-  // Try to remove top level things on at a time.
-  for (DirectParserASTContent child in ast.children) {
-    bool shouldCompile = false;
-    String what = "";
-    if (child.isClass()) {
-      DirectParserASTContentClassDeclarationEnd cls = child.asClass();
-      helper.replacements.add(
-          new Replacement(cls.beginToken.offset - 1, cls.endToken.offset + 1));
-      shouldCompile = true;
-      what = "class";
-    } else if (child.isMixinDeclaration()) {
-      DirectParserASTContentMixinDeclarationEnd decl =
-          child.asMixinDeclaration();
-      helper.replacements.add(new Replacement(
-          decl.mixinKeyword.offset - 1, decl.endToken.offset + 1));
-      shouldCompile = true;
-      what = "mixin";
-    } else if (child.isNamedMixinDeclaration()) {
-      DirectParserASTContentNamedMixinApplicationEnd decl =
-          child.asNamedMixinDeclaration();
-      helper.replacements.add(
-          new Replacement(decl.begin.offset - 1, decl.endToken.offset + 1));
-      shouldCompile = true;
-      what = "named mixin";
-    } else if (child.isExtension()) {
-      DirectParserASTContentExtensionDeclarationEnd decl = child.asExtension();
-      helper.replacements.add(new Replacement(
-          decl.extensionKeyword.offset - 1, decl.endToken.offset + 1));
-      shouldCompile = true;
-      what = "extension";
-    } else if (child.isTopLevelFields()) {
-      DirectParserASTContentTopLevelFieldsEnd decl = child.asTopLevelFields();
-      helper.replacements.add(new Replacement(
-          decl.beginToken.offset - 1, decl.endToken.offset + 1));
-      shouldCompile = true;
-      what = "toplevel fields";
-    } else if (child.isTopLevelMethod()) {
-      DirectParserASTContentTopLevelMethodEnd decl = child.asTopLevelMethod();
-      helper.replacements.add(new Replacement(
-          decl.beginToken.offset - 1, decl.endToken.offset + 1));
-      shouldCompile = true;
-      what = "toplevel method";
-    } else if (child.isEnum()) {
-      DirectParserASTContentEnumEnd decl = child.asEnum();
-      helper.replacements.add(new Replacement(
-          decl.enumKeyword.offset - 1, decl.leftBrace.endGroup.offset + 1));
-      shouldCompile = true;
-      what = "enum";
-    } else if (child.isTypedef()) {
-      DirectParserASTContentFunctionTypeAliasEnd decl = child.asTypedef();
-      helper.replacements.add(new Replacement(
-          decl.typedefKeyword.offset - 1, decl.endToken.offset + 1));
-      shouldCompile = true;
-      what = "typedef";
-    } else if (child.isMetadata()) {
-      DirectParserASTContentMetadataStarEnd decl = child.asMetadata();
-      List<DirectParserASTContentMetadataEnd> metadata =
-          decl.getMetadataEntries();
-      if (metadata.isNotEmpty) {
-        helper.replacements.add(new Replacement(
-            metadata.first.beginToken.offset - 1,
-            metadata.last.endToken.offset));
-        shouldCompile = true;
-      }
-      what = "metadata";
-    } else if (child.isImport()) {
-      DirectParserASTContentImportEnd decl = child.asImport();
-      helper.replacements.add(new Replacement(
-          decl.importKeyword.offset - 1, decl.semicolon.offset + 1));
-      shouldCompile = true;
-      what = "import";
-    } else if (child.isExport()) {
-      DirectParserASTContentExportEnd decl = child.asExport();
-      helper.replacements.add(new Replacement(
-          decl.exportKeyword.offset - 1, decl.semicolon.offset + 1));
-      shouldCompile = true;
-      what = "export";
-    } else if (child.isLibraryName()) {
-      DirectParserASTContentLibraryNameEnd decl = child.asLibraryName();
-      helper.replacements.add(new Replacement(
-          decl.libraryKeyword.offset - 1, decl.semicolon.offset + 1));
-      shouldCompile = true;
-      what = "library name";
-    } else if (child.isPart()) {
-      DirectParserASTContentPartEnd decl = child.asPart();
-      helper.replacements.add(new Replacement(
-          decl.partKeyword.offset - 1, decl.semicolon.offset + 1));
-      shouldCompile = true;
-      what = "part";
-    } else if (child.isPartOf()) {
-      DirectParserASTContentPartOfEnd decl = child.asPartOf();
-      helper.replacements.add(new Replacement(
-          decl.partKeyword.offset - 1, decl.semicolon.offset + 1));
-      shouldCompile = true;
-      what = "part of";
-    } else if (child.isScript()) {
-      var decl = child.asScript();
-      helper.replacements.add(new Replacement(
-          decl.token.offset - 1, decl.token.offset + decl.token.length));
-      shouldCompile = true;
-      what = "script";
-    }
-
-    if (shouldCompile) {
-      bool success =
-          await _tryReplaceAndCompile(helper, uri, initialComponent, what);
-      if (helper.shouldQuit) return;
-      if (!success) {
-        if (child.isClass()) {
-          // Also try to remove all content of the class.
-          DirectParserASTContentClassDeclarationEnd decl = child.asClass();
-          DirectParserASTContentClassOrMixinBodyEnd body =
-              decl.getClassOrMixinBody();
-          if (body.beginToken.offset + 2 < body.endToken.offset) {
-            helper.replacements.add(
-                new Replacement(body.beginToken.offset, body.endToken.offset));
-            what = "class body";
-            success = await _tryReplaceAndCompile(
-                helper, uri, initialComponent, what);
-            if (helper.shouldQuit) return;
-          }
-
-          if (!success) {
-            // Also try to remove members one at a time.
-            for (DirectParserASTContent child in body.children) {
-              shouldCompile = false;
-              if (child is DirectParserASTContentMemberEnd) {
-                if (child.isClassConstructor()) {
-                  DirectParserASTContentClassConstructorEnd memberDecl =
-                      child.getClassConstructor();
-                  helper.replacements.add(new Replacement(
-                      memberDecl.beginToken.offset - 1,
-                      memberDecl.endToken.offset + 1));
-                  what = "class constructor";
-                  shouldCompile = true;
-                } else if (child.isClassFields()) {
-                  DirectParserASTContentClassFieldsEnd memberDecl =
-                      child.getClassFields();
-                  helper.replacements.add(new Replacement(
-                      memberDecl.beginToken.offset - 1,
-                      memberDecl.endToken.offset + 1));
-                  what = "class fields";
-                  shouldCompile = true;
-                } else if (child.isClassMethod()) {
-                  DirectParserASTContentClassMethodEnd memberDecl =
-                      child.getClassMethod();
-                  helper.replacements.add(new Replacement(
-                      memberDecl.beginToken.offset - 1,
-                      memberDecl.endToken.offset + 1));
-                  what = "class method";
-                  shouldCompile = true;
-                } else if (child.isClassFactoryMethod()) {
-                  DirectParserASTContentClassFactoryMethodEnd memberDecl =
-                      child.getClassFactoryMethod();
-                  helper.replacements.add(new Replacement(
-                      memberDecl.beginToken.offset - 1,
-                      memberDecl.endToken.offset + 1));
-                  what = "class factory method";
-                  shouldCompile = true;
-                } else {
-                  // throw "$child --- ${child.children}";
-                  continue;
-                }
-              } else if (child.isMetadata()) {
-                DirectParserASTContentMetadataStarEnd decl = child.asMetadata();
-                List<DirectParserASTContentMetadataEnd> metadata =
-                    decl.getMetadataEntries();
-                if (metadata.isNotEmpty) {
-                  helper.replacements.add(new Replacement(
-                      metadata.first.beginToken.offset - 1,
-                      metadata.last.endToken.offset));
-                  shouldCompile = true;
-                }
-                what = "metadata";
-              }
-              if (shouldCompile) {
-                success = await _tryReplaceAndCompile(
-                    helper, uri, initialComponent, what);
-                if (helper.shouldQuit) return;
-                if (!success) {
-                  DirectParserASTContentBlockFunctionBodyEnd decl;
-                  if (child is DirectParserASTContentMemberEnd) {
-                    if (child.isClassMethod()) {
-                      decl = child.getClassMethod().getBlockFunctionBody();
-                    } else if (child.isClassConstructor()) {
-                      decl = child.getClassConstructor().getBlockFunctionBody();
-                    }
-                  }
-                  if (decl != null &&
-                      decl.beginToken.offset + 2 < decl.endToken.offset) {
-                    helper.replacements.add(new Replacement(
-                        decl.beginToken.offset, decl.endToken.offset));
-                    what = "class member content";
-                    await _tryReplaceAndCompile(
-                        helper, uri, initialComponent, what);
-                    if (helper.shouldQuit) return;
-                  }
-                }
-              }
-            }
-          }
-
-          // Try to remove "extends", "implements" etc.
-          if (decl.getClassExtends().extendsKeyword != null) {
-            helper.replacements.add(new Replacement(
-                decl.getClassExtends().extendsKeyword.offset - 1,
-                body.beginToken.offset));
-            what = "class extends";
-            success = await _tryReplaceAndCompile(
-                helper, uri, initialComponent, what);
-            if (helper.shouldQuit) return;
-          }
-          if (decl.getClassImplements().implementsKeyword != null) {
-            helper.replacements.add(new Replacement(
-                decl.getClassImplements().implementsKeyword.offset - 1,
-                body.beginToken.offset));
-            what = "class implements";
-            success = await _tryReplaceAndCompile(
-                helper, uri, initialComponent, what);
-            if (helper.shouldQuit) return;
-          }
-          if (decl.getClassWithClause() != null) {
-            helper.replacements.add(new Replacement(
-                decl.getClassWithClause().withKeyword.offset - 1,
-                body.beginToken.offset));
-            what = "class with clause";
-            success = await _tryReplaceAndCompile(
-                helper, uri, initialComponent, what);
-            if (helper.shouldQuit) return;
-          }
-        }
-      }
-    }
-  }
-}
-
-class CompilationHelperClass {
-  int compileTry = 0;
-  bool shouldQuit = false;
-  List<Replacement> replacements = [];
-  Uint8List latestCrashData;
-  final Uint8List originalData;
-
-  CompilationHelperClass(this.originalData) : latestCrashData = originalData;
-}
-
-Future<bool> _tryReplaceAndCompile(CompilationHelperClass data, Uri uri,
-    Component initialComponent, String what) async {
-  if (await shouldQuit()) {
-    data.shouldQuit = true;
-    return false;
-  }
-  stdout.write(".");
-  data.compileTry++;
-  if (data.compileTry % 50 == 0) {
-    stdout.write("(at ${data.compileTry})\n");
-  }
-  Uint8List candidate = _replaceRange(data.replacements, data.originalData);
-
-  fs.data[uri] = candidate;
-  if (await crashesOnCompile(initialComponent)) {
-    print("Found $what from "
-        "${data.replacements.last.from} to "
-        "${data.replacements.last.to} "
-        "that can be removed.");
-    data.latestCrashData = candidate;
-    return true;
-  } else {
-    // Couldn't delete that.
-    data.replacements.removeLast();
-    fs.data[uri] = data.latestCrashData;
-    return false;
-  }
-}
-
-class Replacement implements Comparable<Replacement> {
-  final int from;
-  final int to;
-  final Uint8List nullOrReplacement;
-
-  Replacement(this.from, this.to, {this.nullOrReplacement});
-
-  @override
-  int compareTo(Replacement other) {
-    return from - other.from;
-  }
-}
-
-Uint8List _replaceRange(
-    List<Replacement> unsortedReplacements, Uint8List data) {
-  // The below assumes these are sorted.
-  List<Replacement> sortedReplacements =
-      new List<Replacement>.from(unsortedReplacements)..sort();
-  final BytesBuilder builder = new BytesBuilder();
-  int prev = 0;
-  for (int i = 0; i < sortedReplacements.length; i++) {
-    Replacement replacement = sortedReplacements[i];
-    for (int j = prev; j <= replacement.from; j++) {
-      builder.addByte(data[j]);
-    }
-    if (replacement.nullOrReplacement != null) {
-      builder.add(replacement.nullOrReplacement);
-    }
-    prev = replacement.to;
-  }
-  for (int j = prev; j < data.length; j++) {
-    builder.addByte(data[j]);
-  }
-  Uint8List candidate = builder.takeBytes();
-  return candidate;
-}
-
-const int $LF = 10;
-
-void deleteEmptyLines(Uri uri, Component initialComponent) async {
-  Uint8List data = fs.data[uri];
-  List<Uint8List> lines = [];
-  int start = 0;
-  for (int i = 0; i < data.length; i++) {
-    if (data[i] == $LF) {
-      if (i - start > 0) {
-        lines.add(sublist(data, start, i));
-      }
-      start = i + 1;
-    }
-  }
-  if (data.length - start > 0) {
-    lines.add(sublist(data, start, data.length));
-  }
-
-  final BytesBuilder builder = new BytesBuilder();
-  for (int j = 0; j < lines.length; j++) {
-    if (builder.isNotEmpty) {
-      builder.addByte($LF);
-    }
-    builder.add(lines[j]);
-  }
-  Uint8List candidate = builder.takeBytes();
-  if (candidate.length == data.length) return;
-
-  if (await shouldQuit()) return;
-  fs.data[uri] = candidate;
-  if (!await crashesOnCompile(initialComponent)) {
-    // For some reason the empty lines are important.
-    fs.data[uri] = data;
-  } else {
-    print("\nDeleted empty lines.");
-  }
-}
-
-void deleteLines(Uri uri, Component initialComponent) async {
-  // Try to delete "lines".
-  Uint8List data = fs.data[uri];
-  List<Uint8List> lines = [];
-  int start = 0;
-  for (int i = 0; i < data.length; i++) {
-    if (data[i] == $LF) {
-      lines.add(sublist(data, start, i));
-      start = i + 1;
-    }
-  }
-  lines.add(sublist(data, start, data.length));
-  List<bool> include = new List.filled(lines.length, true);
-  Uint8List latestCrashData = data;
-  int length = 1;
-  int i = 0;
-  while (i < lines.length) {
-    if (await shouldQuit()) break;
-    if (skip) {
-      skip = false;
-      break;
-    }
-    stdout.write(".");
-    if (i % 50 == 0) {
-      stdout.write("(at $i of ${lines.length})\n");
-    }
-    if (i + length > lines.length) {
-      length = lines.length - i;
-    }
-    for (int j = i; j < i + length; j++) {
-      include[j] = false;
-    }
-    final BytesBuilder builder = new BytesBuilder();
-    for (int j = 0; j < lines.length; j++) {
-      if (include[j]) {
-        if (builder.isNotEmpty) {
-          builder.addByte($LF);
-        }
-        builder.add(lines[j]);
-      }
-    }
-    Uint8List candidate = builder.takeBytes();
-    fs.data[uri] = candidate;
-    if (!await crashesOnCompile(initialComponent)) {
-      // Didn't crash => Can't remove line i-j.
-      for (int j = i; j < i + length; j++) {
-        include[j] = true;
-      }
-      if (length > 2) {
-        // Retry with length 2 at same i.
-        // The idea here is that for instance formatted json might have lines
-        // looking like
-        // {
-        // }
-        // where deleting just one line makes it invalid.
-        length = 2;
-      } else if (length > 1) {
-        // Retry with length 1 at same i.
-        length = 1;
+      } else if (filename != null) {
+        throw "Already got '$filename', '$arg' is also a filename; "
+            "can only get one";
       } else {
-        // Couldn't with length 1 either.
+        filename = arg;
+      }
+    }
+    if (settings.noPlatform) {
+      int i = 0;
+      while (settings.platformUri == null ||
+          new File.fromUri(settings.platformUri).existsSync()) {
+        settings.platformUri = Uri.base.resolve("nonexisting_$i");
         i++;
       }
     } else {
-      print("\nCan delete line $i (inclusive) - ${i + length} (exclusive) "
-          "(of ${lines.length})");
-      latestCrashData = candidate;
-      i += length;
-      length *= 2;
-    }
-  }
-  fs.data[uri] = latestCrashData;
-}
-
-Component _latestComponent;
-IncrementalCompiler _latestIncrementalCompiler;
-
-Future<bool> crashesOnCompile(Component initialComponent) async {
-  IncrementalCompiler incrementalCompiler;
-  if (noPlatform) {
-    incrementalCompiler = new IncrementalCompiler(setupCompilerContext());
-  } else {
-    incrementalCompiler = new IncrementalCompiler.fromComponent(
-        setupCompilerContext(), initialComponent);
-  }
-  _latestIncrementalCompiler = incrementalCompiler;
-  incrementalCompiler.invalidate(mainUri);
-  try {
-    _latestComponent = await incrementalCompiler.computeDelta();
-    if (serialize) {
-      ByteSink sink = new ByteSink();
-      BinaryPrinter printer = new BinaryPrinter(sink);
-      printer.writeComponentFile(_latestComponent);
-      sink.builder.takeBytes();
-    }
-    for (Uri uri in invalidate) {
-      incrementalCompiler.invalidate(uri);
-      Component delta = await incrementalCompiler.computeDelta();
-      if (serialize) {
-        ByteSink sink = new ByteSink();
-        BinaryPrinter printer = new BinaryPrinter(sink);
-        printer.writeComponentFile(delta);
-        sink.builder.takeBytes();
+      if (settings.platformUri == null) {
+        throw "No platform given. Use --platform=/path/to/platform.dill";
+      }
+      if (!new File.fromUri(settings.platformUri).existsSync()) {
+        throw "The platform file '${settings.platformUri}' doesn't exist";
       }
     }
-    _latestComponent = null; // if it didn't crash this isn't relevant.
-    return false;
-  } catch (e, st) {
-    // Find line with #0 in it.
-    String eWithSt = "$e\n\n$st";
-    List<String> lines = eWithSt.split("\n");
-    String foundLine = "";
-    int lookFor = 0;
-    for (String line in lines) {
-      if (line.startsWith("#$lookFor")) {
-        foundLine += line;
-        lookFor++;
-        if (lookFor >= stackTraceMatches) {
-          break;
-        } else {
-          foundLine += "\n";
-        }
-      }
+    if (filename == null) {
+      throw "Need file to operate on";
     }
-    if (foundLine == null) throw "Unexpected crash without stacktrace: $e";
-    if (expectedCrashLine == null) {
-      print("Got '$foundLine'");
-      expectedCrashLine = foundLine;
-      return true;
-    } else if (foundLine == expectedCrashLine) {
-      return true;
-    } else {
-      if (askAboutRedirectCrashTarget &&
-          !askedAboutRedirect.contains(foundLine)) {
-        print("Crashed, but another place: $foundLine");
-        while (true) {
-          askedAboutRedirect.add(foundLine);
-          print(eWithSt);
-          print("Should we redirect to searching for that? (y/n)");
-          String answer = stdin.readLineSync();
-          if (answer == "yes" || answer == "y") {
-            expectedCrashLine = foundLine;
-            return true;
-          } else if (answer == "no" || answer == "n") {
-            break;
-          } else {
-            print("Didn't get that answer. "
-                "Please answer 'yes, 'y', 'no' or 'n'");
-          }
-        }
-      }
-      return false;
-    }
-  }
-}
-
-Future<Component> getInitialComponent() async {
-  IncrementalCompiler incrementalCompiler =
-      new IncrementalCompiler(setupCompilerContext());
-  Component originalComponent = await incrementalCompiler.computeDelta();
-  return originalComponent;
-}
-
-CompilerContext setupCompilerContext() {
-  CompilerOptions options = getOptions();
-
-  if (nnbd) {
-    options.explicitExperimentalFlags = {ExperimentalFlag.nonNullable: true};
-  }
-  if (experimentalInvalidation) {
-    options.explicitExperimentalFlags ??= {};
-    options.explicitExperimentalFlags[
-        ExperimentalFlag.alternativeInvalidationStrategy] = true;
+    File file = new File(filename);
+    if (!file.existsSync()) throw "File $filename doesn't exist.";
+    settings.mainUri = file.absolute.uri;
   }
 
-  TargetFlags targetFlags = new TargetFlags(
-      enableNullSafety: nnbd, trackWidgetCreation: widgetTransformation);
-  Target target;
-  switch (targetString) {
-    case "VM":
-      target = new VmTarget(targetFlags);
-      break;
-    case "flutter":
-      target = new FlutterTarget(targetFlags);
-      break;
-    case "ddc":
-      target = new DevCompilerTarget(targetFlags);
-      break;
-    default:
-      throw "Unknown target '$target'";
-  }
-  options.target = target;
-  options.fileSystem = fs;
-  options.sdkRoot = null;
-  options.sdkSummary = platformUri;
-  options.omitPlatform = false;
-  options.onDiagnostic = (DiagnosticMessage message) {
-    // don't care.
-  };
-  if (noPlatform) {
-    options.librariesSpecificationUri = null;
-  }
-
-  CompilerContext compilerContext = new CompilerContext(
-      new ProcessedOptions(options: options, inputs: [mainUri]));
-  return compilerContext;
-}
-
-String getFileAsStringContent(Uint8List rawBytes, bool nnbd) {
-  List<int> lineStarts = <int>[];
-
-  Token firstToken = parser_suite.scanRawBytes(rawBytes,
-      nnbd ? scannerConfiguration : scannerConfigurationNonNNBD, lineStarts);
-
-  if (firstToken == null) {
-    throw "Got null token from scanner";
-  }
-
-  ParserTestListener parserTestListener = new ParserTestListener(false);
-  Parser parser = new Parser(parserTestListener);
-  parser.parseUnit(firstToken);
-  String parsedString =
-      parser_suite.tokenStreamToString(firstToken, lineStarts).toString();
-  return parsedString;
-}
-
-ScannerConfiguration scannerConfiguration = new ScannerConfiguration(
-    enableTripleShift: true,
-    enableExtensionMethods: true,
-    enableNonNullable: true);
-
-ScannerConfiguration scannerConfigurationNonNNBD = new ScannerConfiguration(
-    enableTripleShift: true,
-    enableExtensionMethods: true,
-    enableNonNullable: false);
-
-class FakeFileSystem extends FileSystem {
-  bool _redirectAndRecord = true;
-  bool _initialized = false;
-  final Map<Uri, Uint8List> data = {};
-
-  @override
-  FileSystemEntity entityForUri(Uri uri) {
-    return new FakeFileSystemEntity(this, uri);
-  }
-
-  initializeFromJson(Map<String, dynamic> json) {
-    _initialized = true;
-    _redirectAndRecord = json['_redirectAndRecord'];
-    data.clear();
-    List tmp = json['data'];
-    for (int i = 0; i < tmp.length; i += 2) {
-      Uri key = tmp[i] == null ? null : Uri.parse(tmp[i]);
-      if (tmp[i + 1] == null) {
-        data[key] = null;
-      } else if (tmp[i + 1] is String) {
-        data[key] = utf8.encode(tmp[i + 1]);
-      } else {
-        data[key] = Uint8List.fromList(new List<int>.from(tmp[i + 1]));
-      }
-    }
-  }
-
-  Map<String, dynamic> toJson() {
-    List tmp = [];
-    for (var entry in data.entries) {
-      if (entry.value == null) continue;
-      tmp.add(entry.key == null ? null : entry.key.toString());
-      dynamic out = entry.value;
-      if (entry.value != null && entry.value.isNotEmpty) {
-        try {
-          String string = utf8.decode(entry.value);
-          out = string;
-        } catch (e) {
-          // not a string...
-        }
-      }
-      tmp.add(out);
-    }
-    return {
-      '_redirectAndRecord': _redirectAndRecord,
-      'data': tmp,
-    };
-  }
-}
-
-class FakeFileSystemEntity extends FileSystemEntity {
-  final FakeFileSystem fs;
-  final Uri uri;
-  FakeFileSystemEntity(this.fs, this.uri);
-
-  void _ensureCachedIfOk() {
-    if (fs.data.containsKey(uri)) return;
-    if (fs._initialized) return;
-    if (!fs._redirectAndRecord) {
-      throw "Asked for file in non-recording mode that wasn't known";
-    }
-    File f = new File.fromUri(uri);
-    if (!f.existsSync()) {
-      fs.data[uri] = null;
-      return;
-    }
-    fs.data[uri] = f.readAsBytesSync();
-  }
-
-  @override
-  Future<bool> exists() {
-    _ensureCachedIfOk();
-    Uint8List data = fs.data[uri];
-    if (data == null) return Future.value(false);
-    return Future.value(true);
-  }
-
-  @override
-  Future<List<int>> readAsBytes() {
-    _ensureCachedIfOk();
-    Uint8List data = fs.data[uri];
-    if (data == null) throw new FileSystemException(uri, "File doesn't exist.");
-    return Future.value(data);
-  }
-
-  @override
-  Future<String> readAsString() {
-    _ensureCachedIfOk();
-    Uint8List data = fs.data[uri];
-    if (data == null) throw new FileSystemException(uri, "File doesn't exist.");
-    return Future.value(utf8.decode(data));
-  }
+  TestMinimizer testMinimizer = new TestMinimizer(settings);
+  await testMinimizer.tryToMinimize();
 }
diff --git a/pkg/front_end/test/crashing_test_case_minimizer_impl.dart b/pkg/front_end/test/crashing_test_case_minimizer_impl.dart
new file mode 100644
index 0000000..8bda7f4
--- /dev/null
+++ b/pkg/front_end/test/crashing_test_case_minimizer_impl.dart
@@ -0,0 +1,2102 @@
+// 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.
+
+import 'dart:async' show Future, StreamSubscription;
+
+import 'dart:convert' show JsonEncoder, jsonDecode, utf8;
+
+import 'dart:io' show BytesBuilder, File, stdin, stdout;
+
+import 'dart:math' show max;
+
+import 'dart:typed_data' show Uint8List;
+
+import 'package:_fe_analyzer_shared/src/util/relativize.dart'
+    show relativizeUri, isWindows;
+
+import 'package:_fe_analyzer_shared/src/parser/parser.dart'
+    show Listener, Parser;
+
+import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
+    show ErrorToken, ScannerConfiguration, Token;
+
+import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token;
+
+import 'package:dev_compiler/src/kernel/target.dart' show DevCompilerTarget;
+
+import 'package:front_end/src/api_prototype/compiler_options.dart'
+    show CompilerOptions, DiagnosticMessage;
+
+import 'package:front_end/src/api_prototype/experimental_flags.dart'
+    show ExperimentalFlag;
+
+import 'package:front_end/src/api_prototype/file_system.dart'
+    show FileSystem, FileSystemEntity, FileSystemException;
+
+import 'package:front_end/src/base/processed_options.dart'
+    show ProcessedOptions;
+import 'package:front_end/src/fasta/builder/library_builder.dart';
+
+import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext;
+
+import 'package:front_end/src/fasta/incremental_compiler.dart'
+    show IncrementalCompiler;
+
+import 'package:front_end/src/fasta/kernel/utils.dart' show ByteSink;
+import 'package:front_end/src/fasta/messages.dart' show Message;
+import 'package:front_end/src/fasta/util/direct_parser_ast.dart';
+import 'package:front_end/src/fasta/util/direct_parser_ast_helper.dart';
+
+import 'package:front_end/src/fasta/util/textual_outline.dart'
+    show textualOutline;
+
+import 'package:kernel/ast.dart' show Component;
+
+import 'package:kernel/binary/ast_to_binary.dart' show BinaryPrinter;
+
+import 'package:kernel/target/targets.dart' show Target, TargetFlags;
+import 'package:package_config/package_config.dart';
+
+import "package:vm/target/flutter.dart" show FlutterTarget;
+
+import "package:vm/target/vm.dart" show VmTarget;
+
+import 'incremental_load_from_dill_suite.dart' show getOptions;
+
+import 'parser_test_listener.dart' show ParserTestListener;
+
+import 'parser_suite.dart' as parser_suite;
+
+class TestMinimizerSettings {
+  final _FakeFileSystem _fsInitial = new _FakeFileSystem();
+  final _FakeFileSystem _fsNotInitial = new _FakeFileSystem();
+  _FakeFileSystem get _fs {
+    if (_useInitialFs) return _fsInitial;
+    return _fsNotInitial;
+  }
+
+  bool _useInitialFs = true;
+  Uri mainUri;
+  Uri platformUri;
+  bool noPlatform = false;
+  bool experimentalInvalidation = false;
+  bool serialize = false;
+  bool widgetTransformation = false;
+  final List<Uri> invalidate = [];
+  String targetString = "VM";
+  bool oldBlockDelete = false;
+  bool lineDelete = false;
+  bool byteDelete = false;
+  bool askAboutRedirectCrashTarget = false;
+  bool autoUncoverAllCrashes = false;
+  int stackTraceMatches = 1;
+  final Set<String> askedAboutRedirect = {};
+  final List<Map<String, dynamic>> fileSystems = [];
+  final Set<String> allAutoRedirects = {};
+
+  void goToFileSystem(int i) {
+    Map<String, dynamic> fileSystem = fileSystems[i];
+    fileSystems[i] = _fsNotInitial.toJson();
+    _fsNotInitial.initializeFromJson(fileSystem);
+  }
+
+  Map<String, dynamic> toJson() {
+    List<Map<String, dynamic>> fileSystems =
+        new List<Map<String, dynamic>>.from(this.fileSystems);
+    fileSystems.add(_fsNotInitial.toJson());
+    return {
+      'mainUri': mainUri.toString(),
+      'platformUri': platformUri.toString(),
+      'noPlatform': noPlatform,
+      'experimentalInvalidation': experimentalInvalidation,
+      'serialize': serialize,
+      'widgetTransformation': widgetTransformation,
+      'invalidate': invalidate.map((uri) => uri.toString()).toList(),
+      'targetString': targetString,
+      'oldBlockDelete': oldBlockDelete,
+      'lineDelete': lineDelete,
+      'byteDelete': byteDelete,
+      'askAboutRedirectCrashTarget': askAboutRedirectCrashTarget,
+      'autoUncoverAllCrashes': autoUncoverAllCrashes,
+      'stackTraceMatches': stackTraceMatches,
+      'askedAboutRedirect': askedAboutRedirect.toList(),
+      'fileSystems': fileSystems,
+      'allAutoRedirects': allAutoRedirects.toList(),
+    };
+  }
+
+  initializeFromJson(Map<String, dynamic> json) {
+    mainUri = Uri.parse(json["mainUri"]);
+    platformUri = Uri.parse(json["platformUri"]);
+    noPlatform = json["noPlatform"];
+    experimentalInvalidation = json["experimentalInvalidation"];
+    serialize = json["serialize"];
+    widgetTransformation = json["widgetTransformation"];
+    invalidate.clear();
+    invalidate.addAll(
+        (json["invalidate"] as List).map((uriString) => Uri.parse(uriString)));
+    targetString = json["targetString"];
+    oldBlockDelete = json["oldBlockDelete"];
+    lineDelete = json["lineDelete"];
+    byteDelete = json["byteDelete"];
+    askAboutRedirectCrashTarget = json["askAboutRedirectCrashTarget"];
+    autoUncoverAllCrashes = json["autoUncoverAllCrashes"];
+    stackTraceMatches = json["stackTraceMatches"];
+    askedAboutRedirect.clear();
+    askedAboutRedirect.addAll((json["askedAboutRedirect"] as List).cast());
+    fileSystems.clear();
+    fileSystems.addAll((json["fileSystems"] as List).cast());
+    allAutoRedirects.clear();
+    allAutoRedirects.addAll((json["allAutoRedirects"] as List).cast());
+
+    _fsNotInitial.initializeFromJson(fileSystems.removeLast());
+  }
+}
+
+class TestMinimizer {
+  final TestMinimizerSettings _settings;
+  _FakeFileSystem get _fs => _settings._fs;
+  Uri get _mainUri => _settings.mainUri;
+  String _expectedCrashLine;
+  bool _quit = false;
+  bool _skip = false;
+  bool _check = false;
+  int _currentFsNum = -1;
+
+  Component _latestComponent;
+  IncrementalCompiler _latestCrashingIncrementalCompiler;
+  StreamSubscription<List<int>> _stdinSubscription;
+
+  static const int _$LF = 10;
+
+  TestMinimizer(this._settings);
+
+  void _setupStdin() {
+    try {
+      stdin.echoMode = false;
+      stdin.lineMode = false;
+    } catch (e) {
+      print("error setting settings on stdin");
+    }
+    _stdinSubscription = stdin.listen((List<int> event) {
+      if (event.length == 1 && event.single == "q".codeUnits.single) {
+        print("\n\nGot told to quit!\n\n");
+        _quit = true;
+      } else if (event.length == 1 && event.single == "s".codeUnits.single) {
+        print("\n\nGot told to skip!\n\n");
+        _skip = true;
+      } else if (event.length == 1 && event.single == "c".codeUnits.single) {
+        print("\n\nGot told to check!\n\n");
+        _check = true;
+      } else if (event.length == 1 && event.single == "i".codeUnits.single) {
+        print("\n\n--- STATUS INFORMATION START ---\n\n");
+        print("Currently looking for this crash: $_expectedCrashLine\n\n");
+        print("Currently on filesystem #$_currentFsNum out of "
+            "${_settings.fileSystems.length}\n\n");
+
+        int totalFiles = 0;
+        int emptyFiles = 0;
+        int combinedSize = 0;
+        for (Uri uri in _fs.data.keys) {
+          final Uint8List originalBytes = _fs.data[uri];
+          if (originalBytes == null) continue;
+          totalFiles++;
+          if (originalBytes.isEmpty) emptyFiles++;
+          combinedSize += originalBytes.length;
+        }
+        print("Total files left: $totalFiles.");
+        print("Of which empty: $emptyFiles.");
+        print("Combined size left: $combinedSize bytes.");
+        print("\n\n--- STATUS INFORMATION END ---\n\n");
+      } else {
+        print("\n\nGot stdin input: $event\n\n");
+      }
+    });
+  }
+
+  Future tryToMinimize() async {
+    _setupStdin();
+    while (_currentFsNum < _settings.fileSystems.length) {
+      try {
+        if (_currentFsNum >= 0) {
+          print("Replacing filesystem!");
+          _settings.goToFileSystem(_currentFsNum);
+          _expectedCrashLine = null;
+          _latestComponent = null;
+          _latestCrashingIncrementalCompiler = null;
+        }
+        await _tryToMinimizeImpl();
+        if (_currentFsNum + 1 < _settings.fileSystems.length) {
+          // We have more to do --- but we just printed something the user might
+          // want to read. So wait a little before continuing.
+          print("Waiting for 5 seconds before continuing.");
+          await Future.delayed(new Duration(seconds: 5));
+        }
+      } catch (e) {
+        if (e is _DoesntCrashOnInput) {
+          print("Currently doesn't crash (or no longer crashes) the compiler.");
+        } else {
+          print("About to crash. Dumping settings including the filesystem so "
+              "we can (hopefully) continue later.");
+          _dumpToJson();
+          rethrow;
+        }
+      }
+      _currentFsNum++;
+    }
+
+    await _stdinSubscription.cancel();
+  }
+
+  Future _tryToMinimizeImpl() async {
+    // Set main to be basically empty up front.
+    _settings._useInitialFs = true;
+    _fs.data[_mainUri] = utf8.encode("main() {}");
+    Component initialComponent = await _getInitialComponent();
+    print("Compiled initially (without data)");
+    // Remove fake cache.
+    _fs.data.remove(_mainUri);
+    _settings._useInitialFs = false;
+
+    // First assure it actually crash on the input.
+    if (!await _crashesOnCompile(initialComponent)) {
+      throw new _DoesntCrashOnInput();
+    }
+    print("Step #1: We did crash on the input!");
+
+    // All file should now be cached.
+    _fs._redirectAndRecord = false;
+
+    // For all dart files: Parse them as set their source as the parsed source
+    // to "get around" any encoding issues when printing later.
+    Map<Uri, Uint8List> copy = new Map.from(_fs.data);
+    for (Uri uri in _fs.data.keys) {
+      if (await _shouldQuit()) break;
+      String uriString = uri.toString();
+      if (uriString.endsWith(".json") ||
+          uriString.endsWith(".packages") ||
+          uriString.endsWith(".dill") ||
+          _fs.data[uri] == null ||
+          _fs.data[uri].isEmpty) {
+        // skip
+      } else {
+        try {
+          if (_knownByCompiler(uri)) {
+            String parsedString =
+                _getFileAsStringContent(_fs.data[uri], _isUriNnbd(uri));
+            _fs.data[uri] = utf8.encode(parsedString);
+          }
+        } catch (e) {
+          // crash in scanner/parser --- keep original file. This crash might
+          // be what we're looking for!
+        }
+      }
+    }
+    if (!await _crashesOnCompile(initialComponent)) {
+      // Now - for whatever reason - we didn't crash. Restore.
+      _fs.data.clear();
+      _fs.data.addAll(copy);
+    }
+
+    // Operate on one file at a time: Try to delete all content in file.
+    List<Uri> uris = new List<Uri>.from(_fs.data.keys);
+
+    // TODO(jensj): Can we "thread" this?
+    bool changedSome = true;
+    while (changedSome) {
+      if (await _shouldQuit()) break;
+      while (changedSome) {
+        if (await _shouldQuit()) break;
+        changedSome = false;
+        for (int i = 0; i < uris.length; i++) {
+          if (await _shouldQuit()) break;
+          Uri uri = uris[i];
+          if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+          print("About to work on file $i of ${uris.length}");
+          await _deleteContent(uris, i, false, initialComponent);
+          if (_fs.data[uri] == null || _fs.data[uri].isEmpty) {
+            changedSome = true;
+          }
+        }
+      }
+
+      // Try to delete empty files.
+      bool changedSome2 = true;
+      while (changedSome2) {
+        if (await _shouldQuit()) break;
+        changedSome2 = false;
+        for (int i = 0; i < uris.length; i++) {
+          if (await _shouldQuit()) break;
+          Uri uri = uris[i];
+          if (_fs.data[uri] == null || _fs.data[uri].isNotEmpty) continue;
+          print("About to work on file $i of ${uris.length}");
+          await _deleteContent(uris, i, false, initialComponent,
+              deleteFile: true);
+          if (_fs.data[uri] == null) {
+            changedSome = true;
+            changedSome2 = true;
+          }
+        }
+      }
+
+      int left = 0;
+      for (Uri uri in uris) {
+        if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+        left++;
+      }
+      print("There's now $left files of ${_fs.data.length} files left");
+
+      // Operate on one file at a time.
+      for (Uri uri in _fs.data.keys) {
+        if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+        if (await _shouldQuit()) break;
+
+        if (await _tryRemoveIfNotKnownByCompiler(uri, initialComponent)) {
+          if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+          if (await _shouldQuit()) break;
+        }
+
+        if (_check) {
+          _check = false;
+          if (!await _crashesOnCompile(initialComponent)) {
+            throw "Check revealed that the current file system doesn't crash.";
+          } else {
+            print("Check OK!");
+          }
+        }
+
+        print("Now working on $uri");
+
+        int prevLength = _fs.data[uri].length;
+
+        await _deleteBlocks(uri, initialComponent);
+        await _deleteEmptyLines(uri, initialComponent);
+
+        if (_settings.oldBlockDelete) {
+          // Try to delete blocks.
+          await _deleteBlocksOld(uri, initialComponent);
+        }
+
+        if (_settings.lineDelete) {
+          // Try to delete lines.
+          await _deleteLines(uri, initialComponent);
+        }
+
+        print("We're now at ${_fs.data[uri].length} bytes for $uri "
+            "(was $prevLength).");
+        if (prevLength != _fs.data[uri].length) changedSome = true;
+        if (_fs.data[uri].isEmpty) continue;
+
+        if (_settings.byteDelete) {
+          // Now try to delete 'arbitrarily' (for any given start offset do an
+          // exponential binary search).
+          int prevLength = _fs.data[uri].length;
+          while (true) {
+            if (await _shouldQuit()) break;
+            await _binarySearchDeleteData(uri, initialComponent);
+
+            if (_fs.data[uri].length == prevLength) {
+              // No progress.
+              break;
+            } else {
+              print("We're now at ${_fs.data[uri].length} bytes");
+              prevLength = _fs.data[uri].length;
+              changedSome = true;
+            }
+          }
+        }
+      }
+      for (Uri uri in _fs.data.keys) {
+        if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+        if (await _shouldQuit()) break;
+
+        if (await _tryRemoveIfNotKnownByCompiler(uri, initialComponent)) {
+          if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+          if (await _shouldQuit()) break;
+        }
+
+        if (await _attemptInline(uri, initialComponent)) {
+          changedSome = true;
+
+          if (await _tryRemoveIfNotKnownByCompiler(uri, initialComponent)) {
+            if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+            if (await _shouldQuit()) break;
+          }
+        }
+      }
+    }
+
+    if (await _shouldQuit()) {
+      print("\n\nASKED TO QUIT\n\n");
+    } else {
+      print("\n\nDONE\n\n");
+    }
+
+    Uri jsonOut = _dumpToJson();
+
+    if (!await _shouldQuit()) {
+      // Test converting to incremental compiler yaml test.
+      _outputIncrementalCompilerYamlTest();
+      print("\n\n\n");
+
+      for (Uri uri in uris) {
+        if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+        print("Uri $uri has this content:");
+
+        try {
+          String utfDecoded = utf8.decode(_fs.data[uri], allowMalformed: true);
+          print(utfDecoded);
+        } catch (e) {
+          print(_fs.data[uri]);
+          print("(which crashes when trying to decode as utf8)");
+        }
+        print("\n\n====================\n\n");
+      }
+
+      print("Wrote json dump to $jsonOut");
+    }
+  }
+
+  Future<bool> _shouldQuit() async {
+    // allow some time for stdin.listen to process data.
+    await new Future.delayed(new Duration(milliseconds: 5));
+    return _quit;
+  }
+
+  Uri _dumpToJson() {
+    JsonEncoder jsonEncoder = new JsonEncoder.withIndent("  ");
+    String json = jsonEncoder.convert(_settings);
+    int i = 0;
+    Uri jsonOut;
+    while (jsonOut == null || new File.fromUri(jsonOut).existsSync()) {
+      jsonOut = Uri.base.resolve("crash_minimizer_result_$i");
+      i++;
+    }
+    new File.fromUri(jsonOut).writeAsStringSync(json);
+    print("Wrote json dump to $jsonOut");
+    return jsonOut;
+  }
+
+  /// Attempts to inline small files in other files.
+  /// Returns true if anything was changed, i.e. if at least one inlining was a
+  /// success.
+  Future<bool> _attemptInline(Uri uri, Component initialComponent) async {
+    // Don't attempt to inline the main uri --- that's our entry!
+    if (uri == _mainUri) return false;
+
+    Uint8List inlineData = _fs.data[uri];
+    bool hasMultipleLines = false;
+    for (int i = 0; i < inlineData.length; i++) {
+      if (inlineData[i] == _$LF) {
+        hasMultipleLines = true;
+        break;
+      }
+    }
+    // TODO(jensj): Maybe inline slightly bigger files too?
+    if (hasMultipleLines) {
+      return false;
+    }
+
+    Uri inlinableUri = uri;
+
+    int compileTry = 0;
+    bool changed = false;
+
+    for (Uri uri in _fs.data.keys) {
+      if (!uri.toString().endsWith(".dart")) continue;
+      if (inlinableUri == uri) continue;
+      final Uint8List originalBytes = _fs.data[uri];
+      if (originalBytes == null || originalBytes.isEmpty) continue;
+      DirectParserASTContentCompilationUnitEnd ast = getAST(originalBytes,
+          includeBody: false,
+          includeComments: false,
+          enableExtensionMethods: true,
+          enableNonNullable: _isUriNnbd(uri));
+      // Find all imports/exports of this file (if any).
+      // If finding any:
+      // * remove all of them, then
+      // * find the end of the last import/export, and
+      // * insert the content of the file there.
+      // * if that *doesn't* work and we've inserted an export,
+      //   try converting that to an import instead.
+      List<_Replacement> replacements = [];
+      for (DirectParserASTContentImportEnd import in ast.getImports()) {
+        Token importUriToken = import.importKeyword.next;
+        Uri importUri = _getUri(importUriToken, uri);
+        if (inlinableUri == importUri) {
+          replacements.add(new _Replacement(
+              import.importKeyword.offset - 1, import.semicolon.offset + 1));
+        }
+      }
+      for (DirectParserASTContentExportEnd export in ast.getExports()) {
+        Token exportUriToken = export.exportKeyword.next;
+        Uri exportUri = _getUri(exportUriToken, uri);
+        if (inlinableUri == exportUri) {
+          replacements.add(new _Replacement(
+              export.exportKeyword.offset - 1, export.semicolon.offset + 1));
+        }
+      }
+      if (replacements.isEmpty) continue;
+
+      // Step 1: Remove all imports/exports *of* this file (the inlinable file).
+      final Uint8List withoutInlineable =
+          _replaceRange(replacements, originalBytes);
+
+      // Step 2: Find the last import/export.
+      int offsetOfLast = 0;
+      ast = getAST(withoutInlineable,
+          includeBody: false,
+          includeComments: false,
+          enableExtensionMethods: true,
+          enableNonNullable: _isUriNnbd(uri));
+      for (DirectParserASTContentImportEnd import in ast.getImports()) {
+        offsetOfLast = max(offsetOfLast, import.semicolon.offset + 1);
+      }
+      for (DirectParserASTContentExportEnd export in ast.getExports()) {
+        offsetOfLast = max(offsetOfLast, export.semicolon.offset + 1);
+      }
+
+      // Step 3: Insert the content of the file there. Note, though,
+      // that any imports/exports in _that_ file should be changed to be valid
+      // in regards to the new placement.
+      final String withoutInlineableString = utf8.decode(withoutInlineable);
+      StringBuffer builder = new StringBuffer();
+      for (int i = 0; i < offsetOfLast; i++) {
+        builder.writeCharCode(withoutInlineableString.codeUnitAt(i));
+      }
+      builder.write("\n");
+      builder.write(utf8.decode(_rewriteImportsExportsToUri(
+          inlineData, uri, inlinableUri, _isUriNnbd(inlinableUri))));
+      builder.write("\n");
+      for (int i = offsetOfLast; i < withoutInlineableString.length; i++) {
+        builder.writeCharCode(withoutInlineableString.codeUnitAt(i));
+      }
+      final Uint8List inlinedWithoutChange = utf8.encode(builder.toString());
+
+      if (!_parsesWithoutError(inlinedWithoutChange, _isUriNnbd(uri))) {
+        print("WARNING: Parser error after stuff at ${StackTrace.current}");
+      }
+
+      // Step 4: Try it out.
+      if (await _shouldQuit()) break;
+      if (_skip) {
+        _skip = false;
+        break;
+      }
+      stdout.write(".");
+      compileTry++;
+      if (compileTry % 50 == 0) {
+        stdout.write("(at $compileTry)\n");
+      }
+      _fs.data[uri] = inlinedWithoutChange;
+      if (await _crashesOnCompile(initialComponent)) {
+        print("Could inline $inlinableUri into $uri.");
+        changed = true;
+        // File was already updated.
+      } else {
+        // Couldn't replace that.
+        // Insert the original again.
+        _fs.data[uri] = originalBytes;
+
+        // If we've inlined an export, try changing that to an import.
+        builder = new StringBuffer();
+        for (int i = 0; i < offsetOfLast; i++) {
+          builder.writeCharCode(withoutInlineableString.codeUnitAt(i));
+        }
+        builder.write("\n");
+        builder.write(utf8.decode(_rewriteImportsExportsToUri(
+            inlineData, uri, inlinableUri, _isUriNnbd(inlinableUri),
+            convertExportToImport: true)));
+        builder.write("\n");
+        for (int i = offsetOfLast; i < withoutInlineableString.length; i++) {
+          builder.writeCharCode(withoutInlineableString.codeUnitAt(i));
+        }
+        Uint8List inlinedWithChange = utf8.encode(builder.toString());
+
+        if (!_parsesWithoutError(inlinedWithChange, _isUriNnbd(uri))) {
+          print("WARNING: Parser error after stuff at ${StackTrace.current}");
+        }
+
+        // Step 4: Try it out.
+        if (await _shouldQuit()) break;
+        if (_skip) {
+          _skip = false;
+          break;
+        }
+        stdout.write(".");
+        compileTry++;
+        if (compileTry % 50 == 0) {
+          stdout.write("(at $compileTry)\n");
+        }
+        _fs.data[uri] = inlinedWithChange;
+        if (await _crashesOnCompile(initialComponent)) {
+          print("Could inline $inlinableUri into $uri "
+              "(by converting export to import).");
+          changed = true;
+          // File was already updated.
+        } else {
+          // Couldn't replace that.
+          // Insert the original again.
+          _fs.data[uri] = originalBytes;
+        }
+      }
+    }
+
+    return changed;
+  }
+
+  Uint8List _rewriteImportsExportsToUri(
+      Uint8List oldData, Uri newUri, Uri oldUri, bool nnbd,
+      {bool convertExportToImport: false}) {
+    DirectParserASTContentCompilationUnitEnd ast = getAST(oldData,
+        includeBody: false,
+        includeComments: false,
+        enableExtensionMethods: true,
+        enableNonNullable: nnbd);
+    List<_Replacement> replacements = [];
+    for (DirectParserASTContentImportEnd import in ast.getImports()) {
+      _rewriteImportsExportsToUriInternal(
+          import.importKeyword.next, oldUri, replacements, newUri);
+    }
+    for (DirectParserASTContentExportEnd export in ast.getExports()) {
+      if (convertExportToImport) {
+        replacements.add(new _Replacement(
+          export.exportKeyword.offset - 1,
+          export.exportKeyword.offset + export.exportKeyword.length,
+          nullOrReplacement: "import",
+        ));
+      }
+      _rewriteImportsExportsToUriInternal(
+          export.exportKeyword.next, oldUri, replacements, newUri);
+    }
+    if (replacements.isNotEmpty) {
+      Uint8List candidate = _replaceRange(replacements, oldData);
+      return candidate;
+    }
+    return oldData;
+  }
+
+  void _outputIncrementalCompilerYamlTest() {
+    int dartFiles = 0;
+    for (MapEntry<Uri, Uint8List> entry in _fs.data.entries) {
+      if (entry.key.pathSegments.last.endsWith(".dart")) {
+        if (entry.value != null) dartFiles++;
+      }
+    }
+
+    print("------ Reproduction as semi-done incremental yaml test file ------");
+
+    // TODO(jensj): don't use full uris.
+    print("""
+        # 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.md file.
+        
+        # Reproduce a crash.
+        
+        type: newworld""");
+    if (_settings.widgetTransformation) {
+      print("trackWidgetCreation: true");
+      print("target: DDC # basically needed for widget creation to be run");
+    }
+    print("""
+        worlds:
+          - entry: $_mainUri""");
+    if (_settings.experimentalInvalidation) {
+      print("    experiments: alternative-invalidation-strategy");
+    }
+    print("    sources:");
+    for (MapEntry<Uri, Uint8List> entry in _fs.data.entries) {
+      if (entry.value == null) continue;
+      print("      ${entry.key}: |");
+      String string = utf8.decode(entry.value);
+      List<String> lines = string.split("\n");
+      for (String line in lines) {
+        print("        $line");
+      }
+    }
+    print("    expectedLibraryCount: $dartFiles "
+        "# with parts this is not right");
+    print("");
+
+    for (Uri uri in _settings.invalidate) {
+      print("  - entry: $_mainUri");
+      if (_settings.experimentalInvalidation) {
+        print("    experiments: alternative-invalidation-strategy");
+      }
+      print("    worldType: updated");
+      print("    expectInitializeFromDill: false # or true?");
+      print("    invalidate:");
+      print("      - $uri");
+      print("    expectedLibraryCount: $dartFiles "
+          "# with parts this is not right");
+      print("    expectsRebuildBodiesOnly: true # or false?");
+      print("");
+    }
+
+    print("------------------------------------------------------------------");
+  }
+
+  void _rewriteImportsExportsToUriInternal(
+      Token uriToken, Uri oldUri, List<_Replacement> replacements, Uri newUri) {
+    Uri tokenUri = _getUri(uriToken, oldUri, resolvePackage: false);
+    if (tokenUri.scheme == "package" || tokenUri.scheme == "dart") return;
+    Uri asPackageUri = _getImportUri(tokenUri);
+    if (asPackageUri.scheme == "package") {
+      // Just replace with this package uri.
+      replacements.add(new _Replacement(
+        uriToken.offset - 1,
+        uriToken.offset + uriToken.length,
+        nullOrReplacement: '"${asPackageUri.toString()}"',
+      ));
+    } else {
+      String relative = relativizeUri(newUri, tokenUri, isWindows);
+      // TODO(jensj): Maybe if the relative uri becomes too long or has to many
+      // "../../" etc we should just use the absolute uri instead.
+      replacements.add(new _Replacement(
+        uriToken.offset - 1,
+        uriToken.offset + uriToken.length,
+        nullOrReplacement: '"${relative}"',
+      ));
+    }
+  }
+
+  Uri _getUri(Token uriToken, Uri uri, {bool resolvePackage: true}) {
+    String uriString = uriToken.lexeme;
+    uriString = uriString.substring(1, uriString.length - 1);
+    Uri uriTokenUri = uri.resolve(uriString);
+    if (resolvePackage && uriTokenUri.scheme == "package") {
+      Package package = _latestCrashingIncrementalCompiler
+          .currentPackagesMap[uriTokenUri.pathSegments.first];
+      uriTokenUri = package.packageUriRoot
+          .resolve(uriTokenUri.pathSegments.skip(1).join("/"));
+    }
+    return uriTokenUri;
+  }
+
+  Uri _getImportUri(Uri uri) {
+    return _latestCrashingIncrementalCompiler.userCode
+        .getEntryPointUri(uri, issueProblem: false);
+  }
+
+  Uint8List _sublist(Uint8List data, int start, int end) {
+    Uint8List result = new Uint8List(end - start);
+    result.setRange(0, result.length, data, start);
+    return result;
+  }
+
+  void _binarySearchDeleteData(Uri uri, Component initialComponent) async {
+    Uint8List latestCrashData = _fs.data[uri];
+    int offset = 0;
+    while (offset < latestCrashData.length) {
+      print("Working at offset $offset of ${latestCrashData.length}");
+      BytesBuilder builder = new BytesBuilder();
+      builder.add(_sublist(latestCrashData, 0, offset));
+      builder
+          .add(_sublist(latestCrashData, offset + 1, latestCrashData.length));
+      Uint8List candidate = builder.takeBytes();
+      _fs.data[uri] = candidate;
+      if (!await _crashesOnCompile(initialComponent)) {
+        // Deleting 1 char didn't crash; don't try to delete anymore starting
+        // here.
+        offset++;
+        continue;
+      }
+
+      // Find how long we can go.
+      int crashingAt = 1;
+      int noLongerCrashingAt;
+      while (true) {
+        int deleteChars = 2 * crashingAt;
+        if (offset + deleteChars > latestCrashData.length) {
+          deleteChars = latestCrashData.length - offset;
+        }
+        builder = new BytesBuilder();
+        builder.add(_sublist(latestCrashData, 0, offset));
+        builder.add(_sublist(
+            latestCrashData, offset + deleteChars, latestCrashData.length));
+        candidate = builder.takeBytes();
+        _fs.data[uri] = candidate;
+        if (!await _crashesOnCompile(initialComponent)) {
+          noLongerCrashingAt = deleteChars;
+          break;
+        }
+        crashingAt = deleteChars;
+        if (crashingAt + offset == latestCrashData.length) break;
+      }
+
+      if (noLongerCrashingAt == null) {
+        // We can delete the rest.
+        latestCrashData = candidate;
+        continue;
+      }
+
+      // Binary search between [crashingAt] and [noLongerCrashingAt].
+      while (crashingAt < noLongerCrashingAt) {
+        int mid = noLongerCrashingAt -
+            ((noLongerCrashingAt - crashingAt) >>
+                1); // Get middle, rounding up.
+        builder = new BytesBuilder();
+        builder.add(_sublist(latestCrashData, 0, offset));
+        builder.add(
+            _sublist(latestCrashData, offset + mid, latestCrashData.length));
+        candidate = builder.takeBytes();
+        _fs.data[uri] = candidate;
+        if (await _crashesOnCompile(initialComponent)) {
+          crashingAt = mid;
+        } else {
+          // [noLongerCrashingAt] might actually crash now.
+          noLongerCrashingAt = mid - 1;
+        }
+      }
+
+      // This is basically an assert.
+      builder = new BytesBuilder();
+      builder.add(_sublist(latestCrashData, 0, offset));
+      builder.add(_sublist(
+          latestCrashData, offset + crashingAt, latestCrashData.length));
+      candidate = builder.takeBytes();
+      _fs.data[uri] = candidate;
+      if (!await _crashesOnCompile(initialComponent)) {
+        throw "Error in binary search.";
+      }
+      latestCrashData = candidate;
+    }
+
+    _fs.data[uri] = latestCrashData;
+  }
+
+  void _tryToRemoveUnreferencedFileContent(Component initialComponent,
+      {bool deleteFile: false}) async {
+    // Check if there now are any unused files.
+    if (_latestComponent == null) return;
+    Set<Uri> neededUris = _latestComponent.uriToSource.keys.toSet();
+    Map<Uri, Uint8List> copy = new Map.from(_fs.data);
+    bool removedSome = false;
+    if (await _shouldQuit()) return;
+    for (MapEntry<Uri, Uint8List> entry in _fs.data.entries) {
+      if (entry.value == null || entry.value.isEmpty) continue;
+      if (!entry.key.toString().endsWith(".dart")) continue;
+      if (!neededUris.contains(entry.key) && _fs.data[entry.key].length != 0) {
+        if (deleteFile) {
+          _fs.data[entry.key] = null;
+        } else {
+          _fs.data[entry.key] = new Uint8List(0);
+        }
+        print(" => Can probably also delete ${entry.key}");
+        removedSome = true;
+      }
+    }
+    if (removedSome) {
+      if (await _crashesOnCompile(initialComponent)) {
+        print(" => Yes; Could remove those too!");
+      } else {
+        print(" => No; Couldn't remove those too!");
+        _fs.data.clear();
+        _fs.data.addAll(copy);
+      }
+    }
+  }
+
+  void _deleteContent(
+      List<Uri> uris, int uriIndex, bool limitTo1, Component initialComponent,
+      {bool deleteFile: false}) async {
+    String extraMessageText = "all content of ";
+    if (deleteFile) extraMessageText = "";
+
+    if (!limitTo1) {
+      if (await _shouldQuit()) return;
+      Map<Uri, Uint8List> copy = new Map.from(_fs.data);
+      // Try to remove content of i and the next 9 (10 files in total).
+      for (int j = uriIndex; j < uriIndex + 10 && j < uris.length; j++) {
+        Uri uri = uris[j];
+        if (deleteFile) {
+          _fs.data[uri] = null;
+        } else {
+          _fs.data[uri] = new Uint8List(0);
+        }
+      }
+      if (!await _crashesOnCompile(initialComponent)) {
+        // Couldn't delete all 10 files. Restore and try the single one.
+        _fs.data.clear();
+        _fs.data.addAll(copy);
+      } else {
+        for (int j = uriIndex; j < uriIndex + 10 && j < uris.length; j++) {
+          Uri uri = uris[j];
+          print("Can delete ${extraMessageText}file $uri");
+        }
+        await _tryToRemoveUnreferencedFileContent(initialComponent,
+            deleteFile: deleteFile);
+        return;
+      }
+    }
+
+    if (await _shouldQuit()) return;
+    Uri uri = uris[uriIndex];
+    Uint8List data = _fs.data[uri];
+    if (deleteFile) {
+      _fs.data[uri] = null;
+    } else {
+      _fs.data[uri] = new Uint8List(0);
+    }
+    if (!await _crashesOnCompile(initialComponent)) {
+      print(
+          "Can't delete ${extraMessageText}file $uri -- keeping it (for now)");
+      _fs.data[uri] = data;
+
+      // For dart files we can't truncate completely try to "outline" them
+      // instead.
+      if (uri.toString().endsWith(".dart")) {
+        String textualOutlined =
+            textualOutline(data)?.replaceAll(RegExp(r'\n+'), "\n");
+
+        bool outlined = false;
+        if (textualOutlined != null) {
+          Uint8List candidate = utf8.encode(textualOutlined);
+          // Because textual outline doesn't do the right thing for nnbd, only
+          // replace if it's syntactically valid.
+          if (candidate.length != _fs.data[uri].length &&
+              _parsesWithoutError(candidate, _isUriNnbd(uri))) {
+            if (await _shouldQuit()) return;
+            _fs.data[uri] = candidate;
+            if (!await _crashesOnCompile(initialComponent)) {
+              print("Can't outline the file $uri -- keeping it (for now)");
+              _fs.data[uri] = data;
+            } else {
+              outlined = true;
+              print("Can outline the file $uri "
+                  "(now ${_fs.data[uri].length} bytes)");
+            }
+          }
+        }
+        if (!outlined) {
+          // We can probably at least remove all comments then...
+          try {
+            List<String> strings = utf8.decode(_fs.data[uri]).split("\n");
+            List<String> stringsLeft = [];
+            for (String string in strings) {
+              if (!string.trim().startsWith("//")) stringsLeft.add(string);
+            }
+
+            Uint8List candidate = utf8.encode(stringsLeft.join("\n"));
+            if (candidate.length != _fs.data[uri].length) {
+              if (await _shouldQuit()) return;
+              _fs.data[uri] = candidate;
+              if (!await _crashesOnCompile(initialComponent)) {
+                print("Can't remove comments for file $uri -- "
+                    "keeping it (for now)");
+                _fs.data[uri] = data;
+              } else {
+                print("Removed comments for the file $uri");
+              }
+            }
+          } catch (e) {
+            // crash in scanner/parser --- keep original file. This crash might
+            // be what we're looking for!
+          }
+        }
+      }
+    } else {
+      print("Can delete ${extraMessageText}file $uri");
+      await _tryToRemoveUnreferencedFileContent(initialComponent);
+    }
+  }
+
+  void _deleteBlocksOld(Uri uri, Component initialComponent) async {
+    if (uri.toString().endsWith(".json")) {
+      // Try to find annoying
+      //
+      //    },
+      //    {
+      //    }
+      //
+      // part of json and remove it.
+      Uint8List data = _fs.data[uri];
+      String string = utf8.decode(data);
+      List<String> lines = string.split("\n");
+      for (int i = 0; i < lines.length - 2; i++) {
+        if (lines[i].trim() == "}," &&
+            lines[i + 1].trim() == "{" &&
+            lines[i + 2].trim() == "}") {
+          // This is the pattern we wanted to find. Remove it.
+          lines.removeRange(i, i + 2);
+          i--;
+        }
+      }
+      string = lines.join("\n");
+      _fs.data[uri] = utf8.encode(string);
+      if (!await _crashesOnCompile(initialComponent)) {
+        // For some reason that didn't work.
+        _fs.data[uri] = data;
+      }
+    }
+    if (!uri.toString().endsWith(".dart")) return;
+
+    Uint8List data = _fs.data[uri];
+    Uint8List latestCrashData = data;
+
+    List<int> lineStarts = [];
+
+    Token firstToken = parser_suite.scanRawBytes(
+        data,
+        _isUriNnbd(uri) ? _scannerConfiguration : _scannerConfigurationNonNNBD,
+        lineStarts);
+
+    if (firstToken == null) {
+      print("Got null token from scanner for $uri");
+      return;
+    }
+
+    int compileTry = 0;
+    Token token = firstToken;
+    while (token is ErrorToken) {
+      token = token.next;
+    }
+    List<_Replacement> replacements = [];
+    while (token != null && !token.isEof) {
+      bool tryCompile = false;
+      Token skipToToken = token;
+      // Skip very small blocks (e.g. "{}" or "{\n}");
+      if (token.endGroup != null && token.offset + 3 < token.endGroup.offset) {
+        replacements.add(new _Replacement(token.offset, token.endGroup.offset));
+        tryCompile = true;
+        skipToToken = token.endGroup;
+      } else if (token.lexeme == "@") {
+        if (token.next.next.endGroup != null) {
+          int end = token.next.next.endGroup.offset;
+          skipToToken = token.next.next.endGroup;
+          replacements.add(new _Replacement(token.offset - 1, end + 1));
+          tryCompile = true;
+        }
+      } else if (token.lexeme == "assert") {
+        if (token.next.endGroup != null) {
+          int end = token.next.endGroup.offset;
+          skipToToken = token.next.endGroup;
+          if (token.next.endGroup.next.lexeme == ",") {
+            end = token.next.endGroup.next.offset;
+            skipToToken = token.next.endGroup.next;
+          }
+          // +/- 1 to not include the start and the end character.
+          replacements.add(new _Replacement(token.offset - 1, end + 1));
+          tryCompile = true;
+        }
+      } else if ((token.lexeme == "abstract" && token.next.lexeme == "class") ||
+          token.lexeme == "class" ||
+          token.lexeme == "enum" ||
+          token.lexeme == "mixin" ||
+          token.lexeme == "static" ||
+          token.next.lexeme == "get" ||
+          token.next.lexeme == "set" ||
+          token.next.next.lexeme == "(" ||
+          (token.next.lexeme == "<" &&
+              token.next.endGroup != null &&
+              token.next.endGroup.next.next.lexeme == "(")) {
+        // Try to find and remove the entire class/enum/mixin/
+        // static procedure/getter/setter/simple procedure.
+        Token bracket = token;
+        for (int i = 0; i < 20; i++) {
+          // Find "{", but only go a maximum of 20 tokens to do that.
+          bracket = bracket.next;
+          if (bracket.lexeme == "{" && bracket.endGroup != null) {
+            break;
+          } else if ((bracket.lexeme == "(" || bracket.lexeme == "<") &&
+              bracket.endGroup != null) {
+            bracket = bracket.endGroup;
+          }
+        }
+        if (bracket.lexeme == "{" && bracket.endGroup != null) {
+          int end = bracket.endGroup.offset;
+          skipToToken = bracket.endGroup;
+          // +/- 1 to not include the start and the end character.
+          replacements.add(new _Replacement(token.offset - 1, end + 1));
+          tryCompile = true;
+        }
+      }
+
+      if (tryCompile) {
+        if (await _shouldQuit()) break;
+        if (_skip) {
+          _skip = false;
+          break;
+        }
+        stdout.write(".");
+        compileTry++;
+        if (compileTry % 50 == 0) {
+          stdout.write("(at $compileTry)\n");
+        }
+        Uint8List candidate = _replaceRange(replacements, data);
+        _fs.data[uri] = candidate;
+        if (await _crashesOnCompile(initialComponent)) {
+          print("Found block from "
+              "${replacements.last.from} to "
+              "${replacements.last.to} "
+              "that can be removed.");
+          latestCrashData = candidate;
+          token = skipToToken;
+        } else {
+          // Couldn't delete that.
+          replacements.removeLast();
+        }
+      }
+      token = token.next;
+    }
+    _fs.data[uri] = latestCrashData;
+  }
+
+  void _deleteBlocks(final Uri uri, Component initialComponent) async {
+    if (uri.toString().endsWith(".json")) {
+      // Try to find annoying
+      //
+      //    },
+      //    {
+      //    }
+      //
+      // part of json and remove it.
+      Uint8List data = _fs.data[uri];
+      String string = utf8.decode(data);
+      List<String> lines = string.split("\n");
+      for (int i = 0; i < lines.length - 2; i++) {
+        if (lines[i].trim() == "}," &&
+            lines[i + 1].trim() == "{" &&
+            lines[i + 2].trim() == "}") {
+          // This is the pattern we wanted to find. Remove it.
+          lines.removeRange(i, i + 2);
+          i--;
+        }
+      }
+      string = lines.join("\n");
+      Uint8List candidate = utf8.encode(string);
+      if (candidate.length != data.length) {
+        _fs.data[uri] = candidate;
+        if (!await _crashesOnCompile(initialComponent)) {
+          // For some reason that didn't work.
+          _fs.data[uri] = data;
+        }
+      }
+
+      // Try to load json and remove blocks.
+      try {
+        Map json = jsonDecode(utf8.decode(data));
+        Map jsonModified = new Map.from(json);
+        List packages = json["packages"];
+        List packagesModified = new List.from(packages);
+        jsonModified["packages"] = packagesModified;
+        int i = 0;
+        print("Note there's ${packagesModified.length} packages in .json");
+        JsonEncoder jsonEncoder = new JsonEncoder.withIndent("  ");
+        while (i < packagesModified.length) {
+          var oldEntry = packagesModified.removeAt(i);
+          String jsonString = jsonEncoder.convert(jsonModified);
+          candidate = utf8.encode(jsonString);
+          Uint8List previous = _fs.data[uri];
+          _fs.data[uri] = candidate;
+          if (!await _crashesOnCompile(initialComponent)) {
+            // Couldn't remove that part.
+            _fs.data[uri] = previous;
+            packagesModified.insert(i, oldEntry);
+            i++;
+          } else {
+            print("Removed package from .json "
+                "(${packagesModified.length} left).");
+          }
+        }
+      } catch (e) {
+        // Couldn't decode it, so don't try to do anything.
+      }
+      return;
+    }
+    if (!uri.toString().endsWith(".dart")) return;
+
+    Uint8List data = _fs.data[uri];
+    DirectParserASTContentCompilationUnitEnd ast = getAST(data,
+        includeBody: true,
+        includeComments: false,
+        enableExtensionMethods: true,
+        enableNonNullable: _isUriNnbd(uri));
+
+    _CompilationHelperClass helper = new _CompilationHelperClass(data);
+
+    // Try to remove top level things one at a time.
+    for (DirectParserASTContent child in ast.children) {
+      bool shouldCompile = false;
+      String what = "";
+      if (child.isClass()) {
+        DirectParserASTContentClassDeclarationEnd cls = child.asClass();
+        helper.replacements.add(new _Replacement(
+            cls.beginToken.offset - 1, cls.endToken.offset + 1));
+        shouldCompile = true;
+        what = "class";
+      } else if (child.isMixinDeclaration()) {
+        DirectParserASTContentMixinDeclarationEnd decl =
+            child.asMixinDeclaration();
+        helper.replacements.add(new _Replacement(
+            decl.mixinKeyword.offset - 1, decl.endToken.offset + 1));
+        shouldCompile = true;
+        what = "mixin";
+      } else if (child.isNamedMixinDeclaration()) {
+        DirectParserASTContentNamedMixinApplicationEnd decl =
+            child.asNamedMixinDeclaration();
+        helper.replacements.add(
+            new _Replacement(decl.begin.offset - 1, decl.endToken.offset + 1));
+        shouldCompile = true;
+        what = "named mixin";
+      } else if (child.isExtension()) {
+        DirectParserASTContentExtensionDeclarationEnd decl =
+            child.asExtension();
+        helper.replacements.add(new _Replacement(
+            decl.extensionKeyword.offset - 1, decl.endToken.offset + 1));
+        shouldCompile = true;
+        what = "extension";
+      } else if (child.isTopLevelFields()) {
+        DirectParserASTContentTopLevelFieldsEnd decl = child.asTopLevelFields();
+        helper.replacements.add(new _Replacement(
+            decl.beginToken.offset - 1, decl.endToken.offset + 1));
+        shouldCompile = true;
+        what = "toplevel fields";
+      } else if (child.isTopLevelMethod()) {
+        DirectParserASTContentTopLevelMethodEnd decl = child.asTopLevelMethod();
+        helper.replacements.add(new _Replacement(
+            decl.beginToken.offset - 1, decl.endToken.offset + 1));
+        shouldCompile = true;
+        what = "toplevel method";
+      } else if (child.isEnum()) {
+        DirectParserASTContentEnumEnd decl = child.asEnum();
+        helper.replacements.add(new _Replacement(
+            decl.enumKeyword.offset - 1, decl.leftBrace.endGroup.offset + 1));
+        shouldCompile = true;
+        what = "enum";
+      } else if (child.isTypedef()) {
+        DirectParserASTContentFunctionTypeAliasEnd decl = child.asTypedef();
+        helper.replacements.add(new _Replacement(
+            decl.typedefKeyword.offset - 1, decl.endToken.offset + 1));
+        shouldCompile = true;
+        what = "typedef";
+      } else if (child.isMetadata()) {
+        DirectParserASTContentMetadataStarEnd decl = child.asMetadata();
+        List<DirectParserASTContentMetadataEnd> metadata =
+            decl.getMetadataEntries();
+        if (metadata.isNotEmpty) {
+          helper.replacements.add(new _Replacement(
+              metadata.first.beginToken.offset - 1,
+              metadata.last.endToken.offset));
+          shouldCompile = true;
+        }
+        what = "metadata";
+      } else if (child.isImport()) {
+        DirectParserASTContentImportEnd decl = child.asImport();
+        helper.replacements.add(new _Replacement(
+            decl.importKeyword.offset - 1, decl.semicolon.offset + 1));
+        shouldCompile = true;
+        what = "import";
+      } else if (child.isExport()) {
+        DirectParserASTContentExportEnd decl = child.asExport();
+        helper.replacements.add(new _Replacement(
+            decl.exportKeyword.offset - 1, decl.semicolon.offset + 1));
+        shouldCompile = true;
+        what = "export";
+      } else if (child.isLibraryName()) {
+        DirectParserASTContentLibraryNameEnd decl = child.asLibraryName();
+        helper.replacements.add(new _Replacement(
+            decl.libraryKeyword.offset - 1, decl.semicolon.offset + 1));
+        shouldCompile = true;
+        what = "library name";
+      } else if (child.isPart()) {
+        DirectParserASTContentPartEnd decl = child.asPart();
+        helper.replacements.add(new _Replacement(
+            decl.partKeyword.offset - 1, decl.semicolon.offset + 1));
+        shouldCompile = true;
+        what = "part";
+      } else if (child.isPartOf()) {
+        DirectParserASTContentPartOfEnd decl = child.asPartOf();
+        helper.replacements.add(new _Replacement(
+            decl.partKeyword.offset - 1, decl.semicolon.offset + 1));
+        shouldCompile = true;
+        what = "part of";
+      } else if (child.isScript()) {
+        var decl = child.asScript();
+        helper.replacements.add(new _Replacement(
+            decl.token.offset - 1, decl.token.offset + decl.token.length));
+        shouldCompile = true;
+        what = "script";
+      }
+
+      if (shouldCompile) {
+        bool success =
+            await _tryReplaceAndCompile(helper, uri, initialComponent, what);
+        if (helper.shouldQuit) return;
+        if (!success) {
+          if (child.isClass()) {
+            // Also try to remove all content of the class.
+            DirectParserASTContentClassDeclarationEnd decl = child.asClass();
+            DirectParserASTContentClassOrMixinBodyEnd body =
+                decl.getClassOrMixinBody();
+            if (body.beginToken.offset + 2 < body.endToken.offset) {
+              helper.replacements.add(new _Replacement(
+                  body.beginToken.offset, body.endToken.offset));
+              what = "class body";
+              success = await _tryReplaceAndCompile(
+                  helper, uri, initialComponent, what);
+              if (helper.shouldQuit) return;
+            }
+
+            if (!success) {
+              // Also try to remove members one at a time.
+              for (DirectParserASTContent child in body.children) {
+                shouldCompile = false;
+                if (child is DirectParserASTContentMemberEnd) {
+                  if (child.isClassConstructor()) {
+                    DirectParserASTContentClassConstructorEnd memberDecl =
+                        child.getClassConstructor();
+                    helper.replacements.add(new _Replacement(
+                        memberDecl.beginToken.offset - 1,
+                        memberDecl.endToken.offset + 1));
+                    what = "class constructor";
+                    shouldCompile = true;
+                  } else if (child.isClassFields()) {
+                    DirectParserASTContentClassFieldsEnd memberDecl =
+                        child.getClassFields();
+                    helper.replacements.add(new _Replacement(
+                        memberDecl.beginToken.offset - 1,
+                        memberDecl.endToken.offset + 1));
+                    what = "class fields";
+                    shouldCompile = true;
+                  } else if (child.isClassMethod()) {
+                    DirectParserASTContentClassMethodEnd memberDecl =
+                        child.getClassMethod();
+                    helper.replacements.add(new _Replacement(
+                        memberDecl.beginToken.offset - 1,
+                        memberDecl.endToken.offset + 1));
+                    what = "class method";
+                    shouldCompile = true;
+                  } else if (child.isClassFactoryMethod()) {
+                    DirectParserASTContentClassFactoryMethodEnd memberDecl =
+                        child.getClassFactoryMethod();
+                    helper.replacements.add(new _Replacement(
+                        memberDecl.beginToken.offset - 1,
+                        memberDecl.endToken.offset + 1));
+                    what = "class factory method";
+                    shouldCompile = true;
+                  } else {
+                    // throw "$child --- ${child.children}";
+                    continue;
+                  }
+                } else if (child.isMetadata()) {
+                  DirectParserASTContentMetadataStarEnd decl =
+                      child.asMetadata();
+                  List<DirectParserASTContentMetadataEnd> metadata =
+                      decl.getMetadataEntries();
+                  if (metadata.isNotEmpty) {
+                    helper.replacements.add(new _Replacement(
+                        metadata.first.beginToken.offset - 1,
+                        metadata.last.endToken.offset));
+                    shouldCompile = true;
+                  }
+                  what = "metadata";
+                }
+                if (shouldCompile) {
+                  success = await _tryReplaceAndCompile(
+                      helper, uri, initialComponent, what);
+                  if (helper.shouldQuit) return;
+                  if (!success) {
+                    DirectParserASTContentBlockFunctionBodyEnd decl;
+                    if (child is DirectParserASTContentMemberEnd) {
+                      if (child.isClassMethod()) {
+                        decl = child.getClassMethod().getBlockFunctionBody();
+                      } else if (child.isClassConstructor()) {
+                        decl =
+                            child.getClassConstructor().getBlockFunctionBody();
+                      }
+                    }
+                    if (decl != null &&
+                        decl.beginToken.offset + 2 < decl.endToken.offset) {
+                      helper.replacements.add(new _Replacement(
+                          decl.beginToken.offset, decl.endToken.offset));
+                      what = "class member content";
+                      await _tryReplaceAndCompile(
+                          helper, uri, initialComponent, what);
+                      if (helper.shouldQuit) return;
+                    }
+                  }
+                }
+              }
+            }
+
+            // Try to remove "extends", "implements" etc.
+            if (decl.getClassExtends().extendsKeyword != null) {
+              helper.replacements.add(new _Replacement(
+                  decl.getClassExtends().extendsKeyword.offset - 1,
+                  body.beginToken.offset));
+              what = "class extends";
+              success = await _tryReplaceAndCompile(
+                  helper, uri, initialComponent, what);
+              if (helper.shouldQuit) return;
+            }
+            if (decl.getClassImplements().implementsKeyword != null) {
+              helper.replacements.add(new _Replacement(
+                  decl.getClassImplements().implementsKeyword.offset - 1,
+                  body.beginToken.offset));
+              what = "class implements";
+              success = await _tryReplaceAndCompile(
+                  helper, uri, initialComponent, what);
+              if (helper.shouldQuit) return;
+            }
+            if (decl.getClassWithClause() != null) {
+              helper.replacements.add(new _Replacement(
+                  decl.getClassWithClause().withKeyword.offset - 1,
+                  body.beginToken.offset));
+              what = "class with clause";
+              success = await _tryReplaceAndCompile(
+                  helper, uri, initialComponent, what);
+              if (helper.shouldQuit) return;
+            }
+          } else if (child.isMixinDeclaration()) {
+            // Also try to remove all content of the mixin.
+            DirectParserASTContentMixinDeclarationEnd decl =
+                child.asMixinDeclaration();
+            DirectParserASTContentClassOrMixinBodyEnd body =
+                decl.getClassOrMixinBody();
+            if (body.beginToken.offset + 2 < body.endToken.offset) {
+              helper.replacements.add(new _Replacement(
+                  body.beginToken.offset, body.endToken.offset));
+              what = "mixin body";
+              success = await _tryReplaceAndCompile(
+                  helper, uri, initialComponent, what);
+              if (helper.shouldQuit) return;
+            }
+
+            if (!success) {
+              // Also try to remove members one at a time.
+              for (DirectParserASTContent child in body.children) {
+                shouldCompile = false;
+                if (child is DirectParserASTContentMemberEnd) {
+                  if (child.isMixinConstructor()) {
+                    DirectParserASTContentMixinConstructorEnd memberDecl =
+                        child.getMixinConstructor();
+                    helper.replacements.add(new _Replacement(
+                        memberDecl.beginToken.offset - 1,
+                        memberDecl.endToken.offset + 1));
+                    what = "mixin constructor";
+                    shouldCompile = true;
+                  } else if (child.isMixinFields()) {
+                    DirectParserASTContentMixinFieldsEnd memberDecl =
+                        child.getMixinFields();
+                    helper.replacements.add(new _Replacement(
+                        memberDecl.beginToken.offset - 1,
+                        memberDecl.endToken.offset + 1));
+                    what = "mixin fields";
+                    shouldCompile = true;
+                  } else if (child.isMixinMethod()) {
+                    DirectParserASTContentMixinMethodEnd memberDecl =
+                        child.getMixinMethod();
+                    helper.replacements.add(new _Replacement(
+                        memberDecl.beginToken.offset - 1,
+                        memberDecl.endToken.offset + 1));
+                    what = "mixin method";
+                    shouldCompile = true;
+                  } else if (child.isMixinFactoryMethod()) {
+                    DirectParserASTContentMixinFactoryMethodEnd memberDecl =
+                        child.getMixinFactoryMethod();
+                    helper.replacements.add(new _Replacement(
+                        memberDecl.beginToken.offset - 1,
+                        memberDecl.endToken.offset + 1));
+                    what = "mixin factory method";
+                    shouldCompile = true;
+                  } else {
+                    // throw "$child --- ${child.children}";
+                    continue;
+                  }
+                } else if (child.isMetadata()) {
+                  DirectParserASTContentMetadataStarEnd decl =
+                      child.asMetadata();
+                  List<DirectParserASTContentMetadataEnd> metadata =
+                      decl.getMetadataEntries();
+                  if (metadata.isNotEmpty) {
+                    helper.replacements.add(new _Replacement(
+                        metadata.first.beginToken.offset - 1,
+                        metadata.last.endToken.offset));
+                    shouldCompile = true;
+                  }
+                  what = "metadata";
+                }
+                if (shouldCompile) {
+                  success = await _tryReplaceAndCompile(
+                      helper, uri, initialComponent, what);
+                  if (helper.shouldQuit) return;
+                  if (!success) {
+                    DirectParserASTContentBlockFunctionBodyEnd decl;
+                    if (child is DirectParserASTContentMemberEnd) {
+                      if (child.isClassMethod()) {
+                        decl = child.getClassMethod().getBlockFunctionBody();
+                      } else if (child.isClassConstructor()) {
+                        decl =
+                            child.getClassConstructor().getBlockFunctionBody();
+                      }
+                    }
+                    if (decl != null &&
+                        decl.beginToken.offset + 2 < decl.endToken.offset) {
+                      helper.replacements.add(new _Replacement(
+                          decl.beginToken.offset, decl.endToken.offset));
+                      what = "class member content";
+                      await _tryReplaceAndCompile(
+                          helper, uri, initialComponent, what);
+                      if (helper.shouldQuit) return;
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  Future<bool> _tryReplaceAndCompile(_CompilationHelperClass data, Uri uri,
+      Component initialComponent, String what) async {
+    if (await _shouldQuit()) {
+      data.shouldQuit = true;
+      return false;
+    }
+    stdout.write(".");
+    data.compileTry++;
+    if (data.compileTry % 50 == 0) {
+      stdout.write("(at ${data.compileTry})\n");
+    }
+    Uint8List candidate = _replaceRange(data.replacements, data.originalData);
+
+    if (!_parsesWithoutError(candidate, _isUriNnbd(uri))) {
+      print("WARNING: Parser error after stuff at ${StackTrace.current}");
+      _parsesWithoutError(candidate, _isUriNnbd(uri));
+      _parsesWithoutError(data.originalData, _isUriNnbd(uri));
+    }
+
+    _fs.data[uri] = candidate;
+    if (await _crashesOnCompile(initialComponent)) {
+      print("Found $what from "
+          "${data.replacements.last.from} to "
+          "${data.replacements.last.to} "
+          "that can be removed.");
+      data.latestCrashData = candidate;
+      return true;
+    } else {
+      // Couldn't delete that.
+      data.replacements.removeLast();
+      _fs.data[uri] = data.latestCrashData;
+      return false;
+    }
+  }
+
+  void _deleteEmptyLines(Uri uri, Component initialComponent) async {
+    Uint8List data = _fs.data[uri];
+    List<Uint8List> lines = [];
+    int start = 0;
+    for (int i = 0; i < data.length; i++) {
+      if (data[i] == _$LF) {
+        if (i - start > 0) {
+          lines.add(_sublist(data, start, i));
+        }
+        start = i + 1;
+      }
+    }
+    if (data.length - start > 0) {
+      lines.add(_sublist(data, start, data.length));
+    }
+
+    final BytesBuilder builder = new BytesBuilder();
+    for (int j = 0; j < lines.length; j++) {
+      if (builder.isNotEmpty) {
+        builder.addByte(_$LF);
+      }
+      builder.add(lines[j]);
+    }
+    Uint8List candidate = builder.takeBytes();
+    if (candidate.length == data.length) return;
+
+    if (!_parsesWithoutError(candidate, _isUriNnbd(uri))) {
+      print("WARNING: Parser error after stuff at ${StackTrace.current}");
+    }
+
+    if (await _shouldQuit()) return;
+    _fs.data[uri] = candidate;
+    if (!await _crashesOnCompile(initialComponent)) {
+      // For some reason the empty lines are important.
+      _fs.data[uri] = data;
+    } else {
+      print("\nDeleted empty lines.");
+    }
+  }
+
+  void _deleteLines(Uri uri, Component initialComponent) async {
+    // Try to delete "lines".
+    Uint8List data = _fs.data[uri];
+    List<Uint8List> lines = [];
+    int start = 0;
+    for (int i = 0; i < data.length; i++) {
+      if (data[i] == _$LF) {
+        lines.add(_sublist(data, start, i));
+        start = i + 1;
+      }
+    }
+    lines.add(_sublist(data, start, data.length));
+    List<bool> include = new List.filled(lines.length, true);
+    Uint8List latestCrashData = data;
+    int length = 1;
+    int i = 0;
+    while (i < lines.length) {
+      if (await _shouldQuit()) break;
+      if (_skip) {
+        _skip = false;
+        break;
+      }
+      stdout.write(".");
+      if (i % 50 == 0) {
+        stdout.write("(at $i of ${lines.length})\n");
+      }
+      if (i + length > lines.length) {
+        length = lines.length - i;
+      }
+      for (int j = i; j < i + length; j++) {
+        include[j] = false;
+      }
+      final BytesBuilder builder = new BytesBuilder();
+      for (int j = 0; j < lines.length; j++) {
+        if (include[j]) {
+          if (builder.isNotEmpty) {
+            builder.addByte(_$LF);
+          }
+          builder.add(lines[j]);
+        }
+      }
+      Uint8List candidate = builder.takeBytes();
+      _fs.data[uri] = candidate;
+      if (!await _crashesOnCompile(initialComponent)) {
+        // Didn't crash => Can't remove line i-j.
+        for (int j = i; j < i + length; j++) {
+          include[j] = true;
+        }
+        if (length > 2) {
+          // Retry with length 2 at same i.
+          // The idea here is that for instance formatted json might have lines
+          // looking like
+          // {
+          // }
+          // where deleting just one line makes it invalid.
+          length = 2;
+        } else if (length > 1) {
+          // Retry with length 1 at same i.
+          length = 1;
+        } else {
+          // Couldn't with length 1 either.
+          i++;
+        }
+      } else {
+        print("\nCan delete line $i (inclusive) - ${i + length} (exclusive) "
+            "(of ${lines.length})");
+        latestCrashData = candidate;
+        i += length;
+        length *= 2;
+      }
+    }
+    _fs.data[uri] = latestCrashData;
+  }
+
+  Future<bool> _tryRemoveIfNotKnownByCompiler(Uri uri, initialComponent) async {
+    if (_fs.data[uri] == null || _fs.data[uri].isEmpty) return false;
+    if (!uri.toString().endsWith(".dart")) return false;
+
+    if (_knownByCompiler(uri)) return false;
+
+    // Compiler might not know this. Can we delete it?
+    await _deleteContent([uri], 0, true, initialComponent);
+    if (_fs.data[uri] == null || _fs.data[uri].isEmpty) {
+      await _deleteContent([uri], 0, true, initialComponent, deleteFile: true);
+      return true;
+    }
+
+    return false;
+  }
+
+  bool _knownByCompiler(Uri uri) {
+    LibraryBuilder libraryBuilder = _latestCrashingIncrementalCompiler
+        .userCode.loader.builders[_getImportUri(uri)];
+    if (libraryBuilder != null) {
+      return true;
+    }
+    // TODO(jensj): Parts.
+    return false;
+  }
+
+  bool _isUriNnbd(Uri uri) {
+    LibraryBuilder libraryBuilder = _latestCrashingIncrementalCompiler
+        .userCode.loader.builders[_getImportUri(uri)];
+    if (libraryBuilder != null) {
+      return libraryBuilder.isNonNullableByDefault;
+    }
+    print("Couldn't lookup $uri");
+    for (LibraryBuilder libraryBuilder
+        in _latestCrashingIncrementalCompiler.userCode.loader.builders.values) {
+      if (libraryBuilder.importUri == uri) {
+        print("Found $uri as ${libraryBuilder.importUri} "
+            "(!= ${_getImportUri(uri)})");
+        return libraryBuilder.isNonNullableByDefault;
+      }
+    }
+    // This might be parts?
+    throw "Couldn't lookup $uri at all!";
+  }
+
+  Future<bool> _crashesOnCompile(Component initialComponent) async {
+    IncrementalCompiler incrementalCompiler;
+    if (_settings.noPlatform) {
+      incrementalCompiler = new IncrementalCompiler(_setupCompilerContext());
+    } else {
+      incrementalCompiler = new IncrementalCompiler.fromComponent(
+          _setupCompilerContext(), initialComponent);
+    }
+    incrementalCompiler.invalidate(_mainUri);
+    try {
+      _latestComponent = await incrementalCompiler.computeDelta();
+      if (_settings.serialize) {
+        ByteSink sink = new ByteSink();
+        BinaryPrinter printer = new BinaryPrinter(sink);
+        printer.writeComponentFile(_latestComponent);
+        sink.builder.takeBytes();
+      }
+      for (Uri uri in _settings.invalidate) {
+        incrementalCompiler.invalidate(uri);
+        Component delta = await incrementalCompiler.computeDelta();
+        if (_settings.serialize) {
+          ByteSink sink = new ByteSink();
+          BinaryPrinter printer = new BinaryPrinter(sink);
+          printer.writeComponentFile(delta);
+          sink.builder.takeBytes();
+        }
+      }
+      _latestComponent = null; // if it didn't crash this isn't relevant.
+      return false;
+    } catch (e, st) {
+      // Find line with #0 in it.
+      String eWithSt = "$e\n\n$st";
+      List<String> lines = eWithSt.split("\n");
+      String foundLine = "";
+      int lookFor = 0;
+      for (String line in lines) {
+        if (line.startsWith("#$lookFor")) {
+          foundLine += line;
+          lookFor++;
+          if (lookFor >= _settings.stackTraceMatches) {
+            break;
+          } else {
+            foundLine += "\n";
+          }
+        }
+      }
+      if (foundLine == null) throw "Unexpected crash without stacktrace: $e";
+      if (_expectedCrashLine == null) {
+        print("Got '$foundLine'");
+        _expectedCrashLine = foundLine;
+        _latestCrashingIncrementalCompiler = incrementalCompiler;
+        return true;
+      } else if (foundLine == _expectedCrashLine) {
+        _latestCrashingIncrementalCompiler = incrementalCompiler;
+        return true;
+      } else {
+        if (_settings.autoUncoverAllCrashes &&
+            !_settings.allAutoRedirects.contains(foundLine)) {
+          print("Crashed, but another place: $foundLine");
+          print(" ==> Adding to auto redirects!");
+          // Add the current one too, so we don't rediscover that one once we
+          // try minimizing the new ones.
+          _settings.allAutoRedirects.add(_expectedCrashLine);
+          _settings.allAutoRedirects.add(foundLine);
+          _settings.fileSystems.add(_fs.toJson());
+        } else if (_settings.askAboutRedirectCrashTarget &&
+            !_settings.askedAboutRedirect.contains(foundLine)) {
+          print("Crashed, but another place: $foundLine");
+          while (true) {
+            // Add the current one too, so we don't rediscover that again
+            // and asks about going back to it.
+            _settings.askedAboutRedirect.add(_expectedCrashLine);
+            _settings.askedAboutRedirect.add(foundLine);
+            print(eWithSt);
+            print("Should we redirect to searching for that? (y/n)");
+            String answer = stdin.readLineSync();
+            if (answer == "yes" || answer == "y") {
+              _expectedCrashLine = foundLine;
+              _latestCrashingIncrementalCompiler = incrementalCompiler;
+              return true;
+            } else if (answer == "no" || answer == "n") {
+              break;
+            } else {
+              print("Didn't get that answer. "
+                  "Please answer 'yes, 'y', 'no' or 'n'");
+            }
+          }
+        }
+        return false;
+      }
+    }
+  }
+
+  Future<Component> _getInitialComponent() async {
+    IncrementalCompiler incrementalCompiler =
+        new IncrementalCompiler(_setupCompilerContext());
+    Component originalComponent = await incrementalCompiler.computeDelta();
+    return originalComponent;
+  }
+
+  CompilerContext _setupCompilerContext() {
+    CompilerOptions options = getOptions();
+
+    if (_settings.experimentalInvalidation) {
+      options.explicitExperimentalFlags ??= {};
+      options.explicitExperimentalFlags[
+          ExperimentalFlag.alternativeInvalidationStrategy] = true;
+    }
+
+    TargetFlags targetFlags = new TargetFlags(
+        enableNullSafety: false,
+        trackWidgetCreation: _settings.widgetTransformation);
+    Target target;
+    switch (_settings.targetString) {
+      case "VM":
+        target = new VmTarget(targetFlags);
+        break;
+      case "flutter":
+        target = new FlutterTarget(targetFlags);
+        break;
+      case "ddc":
+        target = new DevCompilerTarget(targetFlags);
+        break;
+      default:
+        throw "Unknown target '$target'";
+    }
+    options.target = target;
+    options.fileSystem = _fs;
+    options.sdkRoot = null;
+    options.sdkSummary = _settings.platformUri;
+    options.omitPlatform = false;
+    options.onDiagnostic = (DiagnosticMessage message) {
+      // don't care.
+    };
+    if (_settings.noPlatform) {
+      options.librariesSpecificationUri = null;
+    }
+
+    CompilerContext compilerContext = new CompilerContext(
+        new ProcessedOptions(options: options, inputs: [_mainUri]));
+    return compilerContext;
+  }
+
+  String _getFileAsStringContent(Uint8List rawBytes, bool nnbd) {
+    List<int> lineStarts = [];
+
+    Token firstToken = parser_suite.scanRawBytes(
+        rawBytes,
+        nnbd ? _scannerConfiguration : _scannerConfigurationNonNNBD,
+        lineStarts);
+
+    if (firstToken == null) {
+      throw "Got null token from scanner";
+    }
+
+    ParserTestListener parserTestListener = new ParserTestListener(false);
+    Parser parser = new Parser(parserTestListener);
+    parser.parseUnit(firstToken);
+    String parsedString =
+        parser_suite.tokenStreamToString(firstToken, lineStarts).toString();
+    return parsedString;
+  }
+
+  bool _parsesWithoutError(Uint8List rawBytes, bool nnbd) {
+    Token firstToken = parser_suite.scanRawBytes(rawBytes,
+        nnbd ? _scannerConfiguration : _scannerConfigurationNonNNBD, null);
+
+    if (firstToken == null) {
+      return false;
+    }
+
+    ParserErrorListener parserErrorListener = new ParserErrorListener();
+    Parser parser = new Parser(parserErrorListener);
+    parser.parseUnit(firstToken);
+    return !parserErrorListener.gotError;
+  }
+
+  ScannerConfiguration _scannerConfiguration = new ScannerConfiguration(
+      enableTripleShift: true,
+      enableExtensionMethods: true,
+      enableNonNullable: true);
+
+  ScannerConfiguration _scannerConfigurationNonNNBD = new ScannerConfiguration(
+      enableTripleShift: true,
+      enableExtensionMethods: true,
+      enableNonNullable: false);
+
+  List<int> _dataCache;
+  String _dataCacheString;
+  Uint8List _replaceRange(
+      List<_Replacement> unsortedReplacements, Uint8List rawData) {
+    // The offsets are character offsets, not byte offsets, so for non-ascii
+    // they are not the same so we need to work on the string, not the bytes.
+    if (identical(rawData, _dataCache)) {
+      // cache up to date.
+    } else {
+      _dataCache = rawData;
+      _dataCacheString = utf8.decode(rawData);
+    }
+
+    // The below assumes these are sorted.
+    List<_Replacement> sortedReplacements =
+        new List<_Replacement>.from(unsortedReplacements)..sort();
+    final StringBuffer builder = new StringBuffer();
+    int prev = 0;
+    for (int i = 0; i < sortedReplacements.length; i++) {
+      _Replacement replacement = sortedReplacements[i];
+      for (int j = prev; j <= replacement.from; j++) {
+        builder.writeCharCode(_dataCacheString.codeUnitAt(j));
+      }
+      if (replacement.nullOrReplacement != null) {
+        builder.write(replacement.nullOrReplacement);
+      }
+      prev = replacement.to;
+    }
+    for (int j = prev; j < _dataCacheString.length; j++) {
+      builder.writeCharCode(_dataCacheString.codeUnitAt(j));
+    }
+
+    Uint8List candidate = utf8.encode(builder.toString());
+    return candidate;
+  }
+}
+
+class ParserErrorListener extends Listener {
+  bool gotError = false;
+  List<Message> messages = [];
+  void handleRecoverableError(
+      Message message, Token startToken, Token endToken) {
+    gotError = true;
+    messages.add(message);
+  }
+}
+
+class _CompilationHelperClass {
+  int compileTry = 0;
+  bool shouldQuit = false;
+  List<_Replacement> replacements = [];
+  Uint8List latestCrashData;
+  final Uint8List originalData;
+
+  _CompilationHelperClass(this.originalData) : latestCrashData = originalData;
+}
+
+class _Replacement implements Comparable<_Replacement> {
+  final int from;
+  final int to;
+  final String nullOrReplacement;
+
+  _Replacement(this.from, this.to, {this.nullOrReplacement});
+
+  @override
+  int compareTo(_Replacement other) {
+    return from - other.from;
+  }
+}
+
+class _FakeFileSystem extends FileSystem {
+  bool _redirectAndRecord = true;
+  bool _initialized = false;
+  final Map<Uri, Uint8List> data = {};
+
+  @override
+  FileSystemEntity entityForUri(Uri uri) {
+    return new _FakeFileSystemEntity(this, uri);
+  }
+
+  initializeFromJson(Map<String, dynamic> json) {
+    _initialized = true;
+    _redirectAndRecord = json['_redirectAndRecord'];
+    data.clear();
+    List tmp = json['data'];
+    for (int i = 0; i < tmp.length; i += 2) {
+      Uri key = tmp[i] == null ? null : Uri.parse(tmp[i]);
+      if (tmp[i + 1] == null) {
+        data[key] = null;
+      } else if (tmp[i + 1] is String) {
+        data[key] = utf8.encode(tmp[i + 1]);
+      } else {
+        data[key] = Uint8List.fromList(new List<int>.from(tmp[i + 1]));
+      }
+    }
+  }
+
+  Map<String, dynamic> toJson() {
+    List tmp = [];
+    for (var entry in data.entries) {
+      if (entry.value == null) continue;
+      tmp.add(entry.key == null ? null : entry.key.toString());
+      dynamic out = entry.value;
+      if (entry.value != null && entry.value.isNotEmpty) {
+        try {
+          String string = utf8.decode(entry.value);
+          out = string;
+        } catch (e) {
+          // not a string...
+        }
+      }
+      tmp.add(out);
+    }
+    return {
+      '_redirectAndRecord': _redirectAndRecord,
+      'data': tmp,
+    };
+  }
+}
+
+class _FakeFileSystemEntity extends FileSystemEntity {
+  final _FakeFileSystem fs;
+  final Uri uri;
+  _FakeFileSystemEntity(this.fs, this.uri);
+
+  void _ensureCachedIfOk() {
+    if (fs.data.containsKey(uri)) return;
+    if (fs._initialized) return;
+    if (!fs._redirectAndRecord) {
+      throw "Asked for file in non-recording mode that wasn't known";
+    }
+    File f = new File.fromUri(uri);
+    if (!f.existsSync()) {
+      fs.data[uri] = null;
+      return;
+    }
+    fs.data[uri] = f.readAsBytesSync();
+  }
+
+  @override
+  Future<bool> exists() {
+    _ensureCachedIfOk();
+    Uint8List data = fs.data[uri];
+    if (data == null) return Future.value(false);
+    return Future.value(true);
+  }
+
+  @override
+  Future<List<int>> readAsBytes() {
+    _ensureCachedIfOk();
+    Uint8List data = fs.data[uri];
+    if (data == null) throw new FileSystemException(uri, "File doesn't exist.");
+    return Future.value(data);
+  }
+
+  @override
+  Future<String> readAsString() {
+    _ensureCachedIfOk();
+    Uint8List data = fs.data[uri];
+    if (data == null) throw new FileSystemException(uri, "File doesn't exist.");
+    return Future.value(utf8.decode(data));
+  }
+}
+
+class _DoesntCrashOnInput {
+  _DoesntCrashOnInput();
+}
diff --git a/pkg/front_end/test/fasta/testing/suite.dart b/pkg/front_end/test/fasta/testing/suite.dart
index 64099f8..44c9145 100644
--- a/pkg/front_end/test/fasta/testing/suite.dart
+++ b/pkg/front_end/test/fasta/testing/suite.dart
@@ -838,13 +838,11 @@
       EvaluationMode evaluationMode) {
     constantEvaluator = new ConstantEvaluator(
         backend, environmentDefines, typeEnvironment, this,
-        desugarSets: desugarSets,
         enableTripleShift: enableTripleShift,
         errorOnUnevaluatedConstant: errorOnUnevaluatedConstant,
         evaluationMode: evaluationMode);
     constantEvaluatorWithEmptyEnvironment = new ConstantEvaluator(
         backend, {}, typeEnvironment, this,
-        desugarSets: desugarSets,
         enableTripleShift: enableTripleShift,
         errorOnUnevaluatedConstant: errorOnUnevaluatedConstant,
         evaluationMode: evaluationMode);
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index ef0b0df..49cac17 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -349,6 +349,7 @@
 indents
 initializer2
 inlinable
+inlineable
 instance2
 insufficient
 intdiv
@@ -451,6 +452,7 @@
 micro
 minimize
 minimizer
+minimizing
 mintty
 minutes
 mismatched
@@ -559,6 +561,7 @@
 recompiles
 redir
 redirections
+rediscover
 reducer
 referring
 reflectee
@@ -583,6 +586,7 @@
 retainingpath
 retains
 rev
+revealed
 risky
 rk
 row
@@ -702,6 +706,7 @@
 unawaited
 unbreak
 unconverted
+uncover
 uncovers
 underline
 unpacked
diff --git a/pkg/front_end/testcases/general/constants/from_lib/main.dart b/pkg/front_end/testcases/general/constants/from_lib/main.dart
new file mode 100644
index 0000000..b301857
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/main.dart
@@ -0,0 +1,21 @@
+// 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.
+
+import 'main_lib.dart' as a;
+
+const map = <int, String>{
+  ...a.map,
+};
+
+const set = <int>{
+  ...a.set,
+  ...a.list,
+};
+
+const list = <int>[
+  ...a.list,
+  ...a.set,
+];
+
+main() {}
diff --git a/pkg/front_end/testcases/general/constants/from_lib/main.dart.outline.expect b/pkg/front_end/testcases/general/constants/from_lib/main.dart.outline.expect
new file mode 100644
index 0000000..280c0fe
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/main.dart.outline.expect
@@ -0,0 +1,40 @@
+library;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+
+import "org-dartlang-testcase:///main_lib.dart" as a;
+
+static const field core::Map<core::int*, core::String*>* map = mai::map;
+static const field core::Set<core::int*>* set = mai::set + mai::list;
+static const field core::List<core::int*>* list = mai::list + mai::set;
+static method main() → dynamic
+  ;
+
+library;
+import self as mai;
+import "dart:core" as core;
+
+static const field core::Map<core::int*, core::String*>* map = #C4;
+static const field core::Set<core::int*>* set = #C9;
+static const field core::List<core::int*>* list = #C11;
+
+constants  {
+  #C1 = 1
+  #C2 = "a"
+  #C3 = <dynamic>[#C1, #C2]
+  #C4 = core::_ImmutableMap<core::int*, core::String*> {_kvPairs:#C3}
+  #C5 = 2
+  #C6 = null
+  #C7 = <dynamic>[#C5, #C6]
+  #C8 = core::_ImmutableMap<core::int*, Null> {_kvPairs:#C7}
+  #C9 = col::_UnmodifiableSet<core::int*> {_map:#C8}
+  #C10 = 3
+  #C11 = <core::int*>[#C10]
+}
+
+Extra constant evaluation status:
+Evaluated: MapConcatenation @ org-dartlang-testcase:///main.dart:7:7 -> InstanceConstant(const _ImmutableMap<int*, String*>{_ImmutableMap._kvPairs: const <dynamic>[1, "a"]})
+Evaluated: SetConcatenation @ org-dartlang-testcase:///main.dart:11:18 -> InstanceConstant(const _UnmodifiableSet<int*>{_UnmodifiableSet._map: const _ImmutableMap<int*, Null>{_ImmutableMap._kvPairs: const <dynamic>[2, null, 3, null]}})
+Evaluated: ListConcatenation @ org-dartlang-testcase:///main.dart:16:19 -> ListConstant(const <int*>[3, 2])
+Extra constant evaluation: evaluated: 3, effectively constant: 3
diff --git a/pkg/front_end/testcases/general/constants/from_lib/main.dart.strong.expect b/pkg/front_end/testcases/general/constants/from_lib/main.dart.strong.expect
new file mode 100644
index 0000000..c76837e
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/main.dart.strong.expect
@@ -0,0 +1,36 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+import "org-dartlang-testcase:///main_lib.dart" as a;
+
+static const field core::Map<core::int*, core::String*>* map = #C4;
+static const field core::Set<core::int*>* set = #C10;
+static const field core::List<core::int*>* list = #C11;
+static method main() → dynamic {}
+
+library;
+import self as self2;
+import "dart:core" as core;
+
+static const field core::Map<core::int*, core::String*>* map = #C4;
+static const field core::Set<core::int*>* set = #C14;
+static const field core::List<core::int*>* list = #C15;
+
+constants  {
+  #C1 = 1
+  #C2 = "a"
+  #C3 = <dynamic>[#C1, #C2]
+  #C4 = core::_ImmutableMap<core::int*, core::String*> {_kvPairs:#C3}
+  #C5 = 2
+  #C6 = null
+  #C7 = 3
+  #C8 = <dynamic>[#C5, #C6, #C7, #C6]
+  #C9 = core::_ImmutableMap<core::int*, Null> {_kvPairs:#C8}
+  #C10 = col::_UnmodifiableSet<core::int*> {_map:#C9}
+  #C11 = <core::int*>[#C7, #C5]
+  #C12 = <dynamic>[#C5, #C6]
+  #C13 = core::_ImmutableMap<core::int*, Null> {_kvPairs:#C12}
+  #C14 = col::_UnmodifiableSet<core::int*> {_map:#C13}
+  #C15 = <core::int*>[#C7]
+}
diff --git a/pkg/front_end/testcases/general/constants/from_lib/main.dart.strong.transformed.expect b/pkg/front_end/testcases/general/constants/from_lib/main.dart.strong.transformed.expect
new file mode 100644
index 0000000..c76837e
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/main.dart.strong.transformed.expect
@@ -0,0 +1,36 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+import "org-dartlang-testcase:///main_lib.dart" as a;
+
+static const field core::Map<core::int*, core::String*>* map = #C4;
+static const field core::Set<core::int*>* set = #C10;
+static const field core::List<core::int*>* list = #C11;
+static method main() → dynamic {}
+
+library;
+import self as self2;
+import "dart:core" as core;
+
+static const field core::Map<core::int*, core::String*>* map = #C4;
+static const field core::Set<core::int*>* set = #C14;
+static const field core::List<core::int*>* list = #C15;
+
+constants  {
+  #C1 = 1
+  #C2 = "a"
+  #C3 = <dynamic>[#C1, #C2]
+  #C4 = core::_ImmutableMap<core::int*, core::String*> {_kvPairs:#C3}
+  #C5 = 2
+  #C6 = null
+  #C7 = 3
+  #C8 = <dynamic>[#C5, #C6, #C7, #C6]
+  #C9 = core::_ImmutableMap<core::int*, Null> {_kvPairs:#C8}
+  #C10 = col::_UnmodifiableSet<core::int*> {_map:#C9}
+  #C11 = <core::int*>[#C7, #C5]
+  #C12 = <dynamic>[#C5, #C6]
+  #C13 = core::_ImmutableMap<core::int*, Null> {_kvPairs:#C12}
+  #C14 = col::_UnmodifiableSet<core::int*> {_map:#C13}
+  #C15 = <core::int*>[#C7]
+}
diff --git a/pkg/front_end/testcases/general/constants/from_lib/main.dart.textual_outline.expect b/pkg/front_end/testcases/general/constants/from_lib/main.dart.textual_outline.expect
new file mode 100644
index 0000000..ecc271e
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/main.dart.textual_outline.expect
@@ -0,0 +1,14 @@
+import 'main_lib.dart' as a;
+
+const map = <int, String>{
+  ...a.map,
+};
+const set = <int>{
+  ...a.set,
+  ...a.list,
+};
+const list = <int>[
+  ...a.list,
+  ...a.set,
+];
+main() {}
diff --git a/pkg/front_end/testcases/general/constants/from_lib/main.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/constants/from_lib/main.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..b817c1c
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/main.dart.textual_outline_modelled.expect
@@ -0,0 +1,14 @@
+import 'main_lib.dart' as a;
+
+const list = <int>[
+  ...a.list,
+  ...a.set,
+];
+const map = <int, String>{
+  ...a.map,
+};
+const set = <int>{
+  ...a.set,
+  ...a.list,
+};
+main() {}
diff --git a/pkg/front_end/testcases/general/constants/from_lib/main_lib.dart b/pkg/front_end/testcases/general/constants/from_lib/main_lib.dart
new file mode 100644
index 0000000..eccfc0b
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/main_lib.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.
+
+const map = <int, String>{
+  1: 'a',
+};
+
+const set = <int>{
+  2,
+};
+
+const list = <int>[
+  3,
+];
diff --git a/pkg/front_end/testcases/general/constants/from_lib/test.options b/pkg/front_end/testcases/general/constants/from_lib/test.options
new file mode 100644
index 0000000..bfe6dc8
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/test.options
@@ -0,0 +1 @@
+main_lib.dart
\ No newline at end of file
diff --git a/pkg/front_end/testcases/general/infer_equals.dart b/pkg/front_end/testcases/general/infer_equals.dart
new file mode 100644
index 0000000..5ef9b93
--- /dev/null
+++ b/pkg/front_end/testcases/general/infer_equals.dart
@@ -0,0 +1,16 @@
+// 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.
+
+// @dart=2.6
+
+class Class {
+  var field;
+
+  operator ==(o) {
+    if (o is! Class) return false;
+    return field == o.field;
+  }
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/general/infer_equals.dart.outline.expect b/pkg/front_end/testcases/general/infer_equals.dart.outline.expect
new file mode 100644
index 0000000..1e4df77
--- /dev/null
+++ b/pkg/front_end/testcases/general/infer_equals.dart.outline.expect
@@ -0,0 +1,22 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Class extends core::Object {
+  field dynamic field;
+  synthetic constructor •() → self::Class*
+    ;
+  operator ==(dynamic o) → core::bool*
+    ;
+  abstract member-signature get _identityHashCode() → core::int*; -> core::Object::_identityHashCode
+  abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → core::bool*; -> core::Object::_instanceOf
+  abstract member-signature method _simpleInstanceOf(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOf
+  abstract member-signature method _simpleInstanceOfTrue(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfTrue
+  abstract member-signature method _simpleInstanceOfFalse(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfFalse
+  abstract member-signature get hashCode() → core::int*; -> core::Object::hashCode
+  abstract member-signature method toString() → core::String*; -> core::Object::toString
+  abstract member-signature method noSuchMethod(core::Invocation* invocation) → dynamic; -> core::Object::noSuchMethod
+  abstract member-signature get runtimeType() → core::Type*; -> core::Object::runtimeType
+}
+static method main() → dynamic
+  ;
diff --git a/pkg/front_end/testcases/general/infer_equals.dart.strong.expect b/pkg/front_end/testcases/general/infer_equals.dart.strong.expect
new file mode 100644
index 0000000..f1fea95
--- /dev/null
+++ b/pkg/front_end/testcases/general/infer_equals.dart.strong.expect
@@ -0,0 +1,25 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Class extends core::Object {
+  field dynamic field = null;
+  synthetic constructor •() → self::Class*
+    : super core::Object::•()
+    ;
+  operator ==(dynamic o) → core::bool* {
+    if(!(o is self::Class*))
+      return false;
+    return this.{self::Class::field}.{core::Object::==}(o.field);
+  }
+  abstract member-signature get _identityHashCode() → core::int*; -> core::Object::_identityHashCode
+  abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → core::bool*; -> core::Object::_instanceOf
+  abstract member-signature method _simpleInstanceOf(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOf
+  abstract member-signature method _simpleInstanceOfTrue(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfTrue
+  abstract member-signature method _simpleInstanceOfFalse(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfFalse
+  abstract member-signature get hashCode() → core::int*; -> core::Object::hashCode
+  abstract member-signature method toString() → core::String*; -> core::Object::toString
+  abstract member-signature method noSuchMethod(core::Invocation* invocation) → dynamic; -> core::Object::noSuchMethod
+  abstract member-signature get runtimeType() → core::Type*; -> core::Object::runtimeType
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/general/infer_equals.dart.strong.transformed.expect b/pkg/front_end/testcases/general/infer_equals.dart.strong.transformed.expect
new file mode 100644
index 0000000..f1fea95
--- /dev/null
+++ b/pkg/front_end/testcases/general/infer_equals.dart.strong.transformed.expect
@@ -0,0 +1,25 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Class extends core::Object {
+  field dynamic field = null;
+  synthetic constructor •() → self::Class*
+    : super core::Object::•()
+    ;
+  operator ==(dynamic o) → core::bool* {
+    if(!(o is self::Class*))
+      return false;
+    return this.{self::Class::field}.{core::Object::==}(o.field);
+  }
+  abstract member-signature get _identityHashCode() → core::int*; -> core::Object::_identityHashCode
+  abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → core::bool*; -> core::Object::_instanceOf
+  abstract member-signature method _simpleInstanceOf(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOf
+  abstract member-signature method _simpleInstanceOfTrue(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfTrue
+  abstract member-signature method _simpleInstanceOfFalse(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfFalse
+  abstract member-signature get hashCode() → core::int*; -> core::Object::hashCode
+  abstract member-signature method toString() → core::String*; -> core::Object::toString
+  abstract member-signature method noSuchMethod(core::Invocation* invocation) → dynamic; -> core::Object::noSuchMethod
+  abstract member-signature get runtimeType() → core::Type*; -> core::Object::runtimeType
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/general/infer_equals.dart.textual_outline.expect b/pkg/front_end/testcases/general/infer_equals.dart.textual_outline.expect
new file mode 100644
index 0000000..f3b19e7
--- /dev/null
+++ b/pkg/front_end/testcases/general/infer_equals.dart.textual_outline.expect
@@ -0,0 +1,7 @@
+// @dart = 2.6
+class Class {
+  var field;
+  operator ==(o) {}
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/general/infer_equals.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/infer_equals.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..00f7d50
--- /dev/null
+++ b/pkg/front_end/testcases/general/infer_equals.dart.textual_outline_modelled.expect
@@ -0,0 +1,7 @@
+// @dart = 2.6
+class Class {
+  operator ==(o) {}
+  var field;
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/outline.status b/pkg/front_end/testcases/outline.status
index 8ddb02d..259a2e3 100644
--- a/pkg/front_end/testcases/outline.status
+++ b/pkg/front_end/testcases/outline.status
@@ -14,7 +14,6 @@
 general_nnbd_opt_out/override_setter_with_field: TypeCheckError
 general/abstract_members: TypeCheckError
 general/bug30695: TypeCheckError
-general/constants/const_collections: TypeCheckError
 general/covariant_field: TypeCheckError
 general/getter_vs_setter_type: TypeCheckError
 general/infer_field_from_multiple: TypeCheckError
diff --git a/pkg/kernel/lib/target/targets.dart b/pkg/kernel/lib/target/targets.dart
index 62f7def..be01ba0 100644
--- a/pkg/kernel/lib/target/targets.dart
+++ b/pkg/kernel/lib/target/targets.dart
@@ -90,12 +90,42 @@
   /// Lowering of a list constant to a backend-specific representation.
   Constant lowerListConstant(ListConstant constant) => constant;
 
+  /// Returns `true` if [constant] is lowered list constant created by
+  /// [lowerListConstant].
+  bool isLoweredListConstant(Constant constant) => false;
+
+  /// Calls `f` for each element in the lowered list [constant].
+  ///
+  /// This assumes that `isLoweredListConstant(constant)` is true.
+  void forEachLoweredListConstantElement(
+      Constant constant, void Function(Constant element) f) {}
+
   /// Lowering of a set constant to a backend-specific representation.
   Constant lowerSetConstant(SetConstant constant) => constant;
 
+  /// Returns `true` if [constant] is lowered set constant created by
+  /// [lowerSetConstant].
+  bool isLoweredSetConstant(Constant constant) => false;
+
+  /// Calls `f` for each element in the lowered set [constant].
+  ///
+  /// This assumes that `isLoweredSetConstant(constant)` is true.
+  void forEachLoweredSetConstantElement(
+      Constant constant, void Function(Constant element) f) {}
+
   /// Lowering of a map constant to a backend-specific representation.
   Constant lowerMapConstant(MapConstant constant) => constant;
 
+  /// Returns `true` if [constant] is lowered map constant created by
+  /// [lowerMapConstant].
+  bool isLoweredMapConstant(Constant constant) => false;
+
+  /// Calls `f` for each key/value pair in the lowered map [constant].
+  ///
+  /// This assumes that `lowerMapConstant(constant)` is true.
+  void forEachLoweredMapConstantEntry(
+      Constant constant, void Function(Constant key, Constant value) f) {}
+
   /// Number semantics to use for this backend.
   NumberSemantics get numberSemantics => NumberSemantics.vm;
 
diff --git a/pkg/kernel/lib/type_checker.dart b/pkg/kernel/lib/type_checker.dart
index 1b79258..0e24e6f 100644
--- a/pkg/kernel/lib/type_checker.dart
+++ b/pkg/kernel/lib/type_checker.dart
@@ -706,7 +706,7 @@
   @override
   DartType visitListConcatenation(ListConcatenation node) {
     DartType type =
-        environment.listType(node.typeArgument, currentLibrary.nonNullable);
+        environment.iterableType(node.typeArgument, currentLibrary.nonNullable);
     for (Expression part in node.lists) {
       DartType partType = visitExpression(part);
       checkAssignable(node, type, partType);
@@ -717,7 +717,7 @@
   @override
   DartType visitSetConcatenation(SetConcatenation node) {
     DartType type =
-        environment.setType(node.typeArgument, currentLibrary.nonNullable);
+        environment.iterableType(node.typeArgument, currentLibrary.nonNullable);
     for (Expression part in node.sets) {
       DartType partType = visitExpression(part);
       checkAssignable(node, type, partType);
diff --git a/pkg/kernel/lib/vm/constants_native_effects.dart b/pkg/kernel/lib/vm/constants_native_effects.dart
index 444c44b..8bb1bac 100644
--- a/pkg/kernel/lib/vm/constants_native_effects.dart
+++ b/pkg/kernel/lib/vm/constants_native_effects.dart
@@ -10,8 +10,11 @@
 
 class VmConstantsBackend extends ConstantsBackend {
   final Class immutableMapClass;
+  final Class unmodifiableSetClass;
+  final Field unmodifiableSetMap;
 
-  VmConstantsBackend._(this.immutableMapClass);
+  VmConstantsBackend._(this.immutableMapClass, this.unmodifiableSetMap,
+      this.unmodifiableSetClass);
 
   /// If [defines] is not `null` it will be used for handling
   /// `const {bool,...}.fromEnvironment()` otherwise the current VM's values
@@ -21,8 +24,11 @@
     final Class immutableMapClass = coreLibrary.classes
         .firstWhere((Class klass) => klass.name == '_ImmutableMap');
     assert(immutableMapClass != null);
+    Field unmodifiableSetMap = coreTypes.index
+        .getMember('dart:collection', '_UnmodifiableSet', '_map');
 
-    return new VmConstantsBackend._(immutableMapClass);
+    return new VmConstantsBackend._(immutableMapClass, unmodifiableSetMap,
+        unmodifiableSetMap.enclosingClass);
   }
 
   @override
@@ -50,4 +56,63 @@
       kvPairListField.getterReference: kvListConstant,
     });
   }
+
+  @override
+  bool isLoweredMapConstant(Constant constant) {
+    return constant is InstanceConstant &&
+        constant.classNode == immutableMapClass;
+  }
+
+  @override
+  void forEachLoweredMapConstantEntry(
+      Constant constant, void Function(Constant key, Constant value) f) {
+    assert(isLoweredMapConstant(constant));
+    final InstanceConstant instance = constant;
+    assert(immutableMapClass.fields.length == 1);
+    final Field kvPairListField = immutableMapClass.fields[0];
+    final ListConstant kvListConstant =
+        instance.fieldValues[kvPairListField.getterReference];
+    assert(kvListConstant.entries.length % 2 == 0);
+    for (int index = 0; index < kvListConstant.entries.length; index += 2) {
+      f(kvListConstant.entries[index], kvListConstant.entries[index + 1]);
+    }
+  }
+
+  @override
+  Constant lowerSetConstant(SetConstant constant) {
+    final DartType elementType = constant.typeArgument;
+    final List<Constant> entries = constant.entries;
+    final List<ConstantMapEntry> mapEntries =
+        new List<ConstantMapEntry>.filled(entries.length, null);
+    for (int i = 0; i < entries.length; ++i) {
+      mapEntries[i] = new ConstantMapEntry(entries[i], new NullConstant());
+    }
+    Constant map = lowerMapConstant(
+        new MapConstant(elementType, const NullType(), mapEntries));
+    return new InstanceConstant(unmodifiableSetClass.reference, [elementType],
+        <Reference, Constant>{unmodifiableSetMap.getterReference: map});
+  }
+
+  @override
+  bool isLoweredSetConstant(Constant constant) {
+    if (constant is InstanceConstant &&
+        constant.classNode == unmodifiableSetClass) {
+      InstanceConstant instance = constant;
+      return isLoweredMapConstant(
+          instance.fieldValues[unmodifiableSetMap.getterReference]);
+    }
+    return false;
+  }
+
+  @override
+  void forEachLoweredSetConstantElement(
+      Constant constant, void Function(Constant element) f) {
+    assert(isLoweredSetConstant(constant));
+    final InstanceConstant instance = constant;
+    final Constant mapConstant =
+        instance.fieldValues[unmodifiableSetMap.getterReference];
+    forEachLoweredMapConstantEntry(mapConstant, (Constant key, Constant value) {
+      f(key);
+    });
+  }
 }
diff --git a/pkg/vm/test/incremental_compiler_test.dart b/pkg/vm/test/incremental_compiler_test.dart
index 00a1a1c..179c37b 100644
--- a/pkg/vm/test/incremental_compiler_test.dart
+++ b/pkg/vm/test/incremental_compiler_test.dart
@@ -134,7 +134,9 @@
   /// If [getAllSources] is false it will ask specifically for report
   /// (and thus hits) for "lib1.dart" only.
   Future<Set<int>> collectAndCheckCoverageData(int port, bool getAllSources,
-      {bool resume: true}) async {
+      {bool resume: true,
+      bool onGetAllVerifyCount: true,
+      Set<int> coverageForLines}) async {
     RemoteVm remoteVm = new RemoteVm(port);
 
     // Wait for the script to have finished.
@@ -189,7 +191,7 @@
         }
         i++;
       }
-      if (getAllSources) {
+      if (getAllSources && onGetAllVerifyCount) {
         expect(scriptIdToIndex.length >= 2, isTrue);
       }
 
@@ -226,12 +228,16 @@
             }
           }
           for (int pos in coverage["misses"]) positions.add(pos);
-          for (int pos in range["possibleBreakpoints"]) positions.add(pos);
+          if (range["possibleBreakpoints"] != null) {
+            for (int pos in range["possibleBreakpoints"]) positions.add(pos);
+          }
           Map script = scriptIndexToScript[range["scriptIndex"]];
           Set<int> knownPositions = new Set<int>();
+          Map<int, int> tokenPosToLine = {};
           if (script["tokenPosTable"] != null) {
             for (List tokenPosTableLine in script["tokenPosTable"]) {
               for (int i = 1; i < tokenPosTableLine.length; i += 2) {
+                tokenPosToLine[tokenPosTableLine[i]] = tokenPosTableLine[0];
                 knownPositions.add(tokenPosTableLine[i]);
               }
             }
@@ -244,6 +250,14 @@
                   "line and column.");
             }
           }
+
+          if (coverageForLines != null) {
+            for (int pos in coverage["hits"]) {
+              if (lib1scriptIndices.contains(range["scriptIndex"])) {
+                coverageForLines.add(tokenPosToLine[pos]);
+              }
+            }
+          }
         }
       }
     }
@@ -486,6 +500,243 @@
     });
   });
 
+  group('multiple kernels constant coverage', () {
+    Directory mytest;
+    File main;
+    File lib1;
+    int lineForUnnamedConstructor;
+    int lineForNamedConstructor;
+    Process vm;
+    setUpAll(() {
+      mytest = Directory.systemTemp.createTempSync('incremental');
+      main = new File('${mytest.path}/main.dart')..createSync();
+      main.writeAsStringSync("""
+        // This file - combined with the lib - should have coverage for both
+        // constructors of Foo.
+        import 'lib1.dart' as lib1;
+
+        void testFunction() {
+          const foo = lib1.Foo.named();
+          const foo2 = lib1.Foo.named();
+          if (!identical(foo, foo2)) throw "what?";
+        }
+
+        main() {
+          lib1.testFunction();
+          testFunction();
+          print("main");
+        }
+      """);
+      lib1 = new File('${mytest.path}/lib1.dart')..createSync();
+      lib1.writeAsStringSync("""
+        // Compiling this file should mark the default constructor - but not the
+        // named constructor - as having coverage.
+        class Foo {
+          final int x;
+          const Foo([int? x]) : this.x = x ?? 42;
+          const Foo.named([int? x]) : this.x = x ?? 42;
+        }
+
+        void testFunction() {
+          const foo = Foo();
+          const foo2 = Foo();
+          if (!identical(foo, foo2)) throw "what?";
+        }
+
+        main() {
+          testFunction();
+          print("lib1");
+        }
+      """);
+      lineForUnnamedConstructor = 5;
+      lineForNamedConstructor = 6;
+    });
+
+    tearDownAll(() {
+      try {
+        mytest.deleteSync(recursive: true);
+      } catch (_) {
+        // Ignore errors;
+      }
+      try {
+        vm.kill();
+      } catch (_) {
+        // Ignore errors;
+      }
+    });
+
+    Future<Set<int>> runAndGetLineCoverage(
+        File list, String expectStdoutContains) async {
+      vm = await Process.start(Platform.resolvedExecutable, <String>[
+        "--pause-isolates-on-exit",
+        "--enable-vm-service:0",
+        "--disable-service-auth-codes",
+        "--disable-dart-dev",
+        list.path
+      ]);
+
+      const kObservatoryListening = 'Observatory listening on ';
+      final RegExp observatoryPortRegExp =
+          new RegExp("Observatory listening on http://127.0.0.1:\([0-9]*\)");
+      int port;
+      final splitter = new LineSplitter();
+      Completer<String> portLineCompleter = new Completer<String>();
+      Set<int> coverageLines = {};
+      bool foundExpectedString = false;
+      vm.stdout
+          .transform(utf8.decoder)
+          .transform(splitter)
+          .listen((String s) async {
+        if (s == expectStdoutContains) {
+          foundExpectedString = true;
+        }
+        if (s.startsWith(kObservatoryListening)) {
+          expect(observatoryPortRegExp.hasMatch(s), isTrue);
+          final match = observatoryPortRegExp.firstMatch(s);
+          port = int.parse(match.group(1));
+          await collectAndCheckCoverageData(port, true,
+              onGetAllVerifyCount: false, coverageForLines: coverageLines);
+          if (!portLineCompleter.isCompleted) {
+            portLineCompleter.complete("done");
+          }
+        }
+        print("vm stdout: $s");
+      });
+      vm.stderr.transform(utf8.decoder).transform(splitter).listen((String s) {
+        print("vm stderr: $s");
+      });
+      await portLineCompleter.future;
+      print("Compiler terminated with ${await vm.exitCode} exit code");
+      expect(foundExpectedString, isTrue);
+      return coverageLines;
+    }
+
+    test('compile seperatly, check coverage', () async {
+      Directory dir = mytest.createTempSync();
+
+      // First compile lib, run and verify coverage (un-named constructor
+      // covered, but not the named constructor).
+      // Note that it's called 'lib1' to match with expectations from coverage
+      // collector helper in this file.
+      File libDill = File(p.join(dir.path, p.basename(lib1.path + ".dill")));
+      IncrementalCompiler compiler = new IncrementalCompiler(options, lib1.uri);
+      Component component = await compiler.compile();
+      expect(component.libraries.length, equals(1));
+      expect(component.libraries.single.fileUri, equals(lib1.uri));
+      IOSink sink = libDill.openWrite();
+      BinaryPrinter printer = new BinaryPrinter(sink);
+      printer.writeComponentFile(component);
+      await sink.flush();
+      await sink.close();
+      File list = new File(p.join(dir.path, 'dill.list'))..createSync();
+      list.writeAsStringSync("#@dill\n${libDill.path}\n");
+      Set<int> lineCoverage = await runAndGetLineCoverage(list, "lib1");
+      // Expect coverage for unnamed constructor but not for the named one.
+      expect(
+          lineCoverage.intersection(
+              {lineForUnnamedConstructor, lineForNamedConstructor}),
+          equals({lineForUnnamedConstructor}));
+
+      try {
+        vm.kill();
+      } catch (_) {
+        // Ignore errors;
+      }
+      // Accept the compile to not include the lib again.
+      compiler.accept();
+
+      // Then compile lib, run and verify coverage (un-named constructor
+      // covered, and the named constructor coveraged too).
+      File mainDill = File(p.join(dir.path, p.basename(main.path + ".dill")));
+      component = await compiler.compile(entryPoint: main.uri);
+      expect(component.libraries.length, equals(1));
+      expect(component.libraries.single.fileUri, equals(main.uri));
+      sink = mainDill.openWrite();
+      printer = new BinaryPrinter(sink);
+      printer.writeComponentFile(component);
+      await sink.flush();
+      await sink.close();
+      list.writeAsStringSync("#@dill\n${mainDill.path}\n${libDill.path}\n");
+      lineCoverage = await runAndGetLineCoverage(list, "main");
+
+      // Expect coverage for both unnamed constructor and for the named one.
+      expect(
+          lineCoverage.intersection(
+              {lineForUnnamedConstructor, lineForNamedConstructor}),
+          equals({lineForUnnamedConstructor, lineForNamedConstructor}));
+
+      try {
+        vm.kill();
+      } catch (_) {
+        // Ignore errors;
+      }
+      // Accept the compile to not include the lib again.
+      compiler.accept();
+
+      // Finally, change lib to shift the constructors so the old line numbers
+      // doesn't match. Compile lib by itself, compile lib, run with the old
+      // main and verify coverage is still correct (both un-named constructor
+      // and named constructor (at new line numbers) are covered, and the old
+      // line numbers are not coverage.
+
+      lib1.writeAsStringSync("""
+        //
+        // Shift lines down by five
+        // lines so the original
+        // lines can't be coverred
+        //
+        class Foo {
+          final int x;
+          const Foo([int? x]) : this.x = x ?? 42;
+          const Foo.named([int? x]) : this.x = x ?? 42;
+        }
+
+        void testFunction() {
+          const foo = Foo();
+          const foo2 = Foo();
+          if (!identical(foo, foo2)) throw "what?";
+        }
+
+        main() {
+          testFunction();
+          print("lib1");
+        }
+      """);
+      int newLineForUnnamedConstructor = 8;
+      int newLineForNamedConstructor = 9;
+      compiler.invalidate(lib1.uri);
+      component = await compiler.compile(entryPoint: lib1.uri);
+      expect(component.libraries.length, equals(1));
+      expect(component.libraries.single.fileUri, equals(lib1.uri));
+      sink = libDill.openWrite();
+      printer = new BinaryPrinter(sink);
+      printer.writeComponentFile(component);
+      await sink.flush();
+      await sink.close();
+      list.writeAsStringSync("#@dill\n${mainDill.path}\n${libDill.path}\n");
+      lineCoverage = await runAndGetLineCoverage(list, "main");
+
+      // Expect coverage for both unnamed constructor and for the named one on
+      // the new positions, but no coverage on the old positions.
+      expect(
+          lineCoverage.intersection({
+            lineForUnnamedConstructor,
+            lineForNamedConstructor,
+            newLineForUnnamedConstructor,
+            newLineForNamedConstructor
+          }),
+          equals({newLineForUnnamedConstructor, newLineForNamedConstructor}));
+
+      try {
+        vm.kill();
+      } catch (_) {
+        // Ignore errors;
+      }
+      // Accept the compile to not include the lib again.
+      compiler.accept();
+    });
+  });
+
   group('multiple kernels 2', () {
     Directory mytest;
     File main;
diff --git a/runtime/observatory/tests/service/get_source_report_const_coverage_lib.dart b/runtime/observatory/tests/service/get_source_report_const_coverage_lib.dart
new file mode 100644
index 0000000..b0f078d
--- /dev/null
+++ b/runtime/observatory/tests/service/get_source_report_const_coverage_lib.dart
@@ -0,0 +1,12 @@
+// 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.
+
+import "get_source_report_const_coverage_test.dart";
+
+void testFunction() {
+  const namedFoo = Foo.named3();
+  const namedFoo2 = Foo.named3();
+  const namedIdentical = identical(namedFoo, namedFoo2);
+  print("namedIdentical: $namedIdentical");
+}
diff --git a/runtime/observatory/tests/service/get_source_report_const_coverage_test.dart b/runtime/observatory/tests/service/get_source_report_const_coverage_test.dart
new file mode 100644
index 0000000..32cb035
--- /dev/null
+++ b/runtime/observatory/tests/service/get_source_report_const_coverage_test.dart
@@ -0,0 +1,129 @@
+// 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.
+
+import 'package:observatory/service_io.dart';
+import 'package:test/test.dart';
+import 'test_helper.dart';
+import 'service_test_common.dart';
+import 'dart:developer';
+
+import 'get_source_report_const_coverage_lib.dart' as lib;
+
+const String filename = "get_source_report_const_coverage_test";
+const Set<int> expectedLinesHit = {20, 22, 26};
+const Set<int> expectedLinesNotHit = {24};
+
+class Foo {
+  final int x;
+  // Expect this constructor to be coverage by coverage.
+  const Foo([int? x]) : this.x = x ?? 42;
+  // Expect this constructor to be coverage by coverage too.
+  const Foo.named1([int? x]) : this.x = x ?? 42;
+  // Expect this constructor to *NOT* be coverage by coverage.
+  const Foo.named2([int? x]) : this.x = x ?? 42;
+  // Expect this constructor to be coverage by coverage too (from lib).
+  const Foo.named3([int? x]) : this.x = x ?? 42;
+}
+
+void testFunction() {
+  const foo = Foo();
+  const foo2 = Foo();
+  const fooIdentical = identical(foo, foo2);
+  print(fooIdentical);
+
+  const namedFoo = Foo.named1();
+  const namedFoo2 = Foo.named1();
+  const namedIdentical = identical(namedFoo, namedFoo2);
+  print(fooIdentical);
+
+  debugger();
+
+  // That this is called after (or at all) is not relevent for the code
+  // coverage of constants.
+  lib.testFunction();
+
+  print("Done");
+}
+
+var tests = <IsolateTest>[
+  hasStoppedAtBreakpoint,
+  (Isolate isolate) async {
+    final stack = await isolate.getStack();
+
+    // Make sure we are in the right place.
+    expect(stack.type, equals('Stack'));
+    expect(stack['frames'].length, greaterThanOrEqualTo(1));
+    expect(stack['frames'][0].function.name, equals('testFunction'));
+
+    final List<Script> scripts = await isolate.getScripts();
+    Script? foundScript;
+    for (Script script in scripts) {
+      if (script.uri.contains(filename)) {
+        foundScript = script;
+        break;
+      }
+    }
+
+    Set<int> hits;
+    {
+      // Get report for everything; then collect for this library.
+      final Map<String, Object> params = {
+        'reports': ['Coverage'],
+      };
+      final coverage =
+          await isolate.invokeRpcNoUpgrade('getSourceReport', params);
+      hits = getHitsFor(coverage, filename);
+      await foundScript!.load();
+      final Set<int> lines = {};
+      for (int hit in hits) {
+        // We expect every hit to be translatable to line
+        // (i.e. tokenToLine to return non-null).
+        lines.add(foundScript.tokenToLine(hit)!);
+      }
+      print("Token position hits: $hits --- line hits: $lines");
+      expect(lines.intersection(expectedLinesHit), equals(expectedLinesHit));
+      expect(lines.intersection(expectedLinesNotHit), isEmpty);
+    }
+    {
+      // Now get report for the this file only.
+      final Map<String, Object> params = {
+        'reports': ['Coverage'],
+        'scriptId': foundScript.id!
+      };
+      final coverage =
+          await isolate.invokeRpcNoUpgrade('getSourceReport', params);
+      final Set<int> localHits = getHitsFor(coverage, filename);
+      expect(localHits.length, equals(hits.length));
+      expect(hits.toList()..sort(), equals(localHits.toList()..sort()));
+      print(localHits);
+    }
+  },
+];
+
+Set<int> getHitsFor(Map coverage, String uriContains) {
+  final List scripts = coverage["scripts"];
+  final Set<int> scriptIdsWanted = {};
+  for (int i = 0; i < scripts.length; i++) {
+    final Map script = scripts[i];
+    final String scriptUri = script["uri"];
+    if (scriptUri.contains(uriContains)) {
+      scriptIdsWanted.add(i);
+    }
+  }
+  final List ranges = coverage["ranges"];
+  final Set<int> hits = {};
+  for (int i = 0; i < ranges.length; i++) {
+    final Map range = ranges[i];
+    if (scriptIdsWanted.contains(range["scriptIndex"])) {
+      if (range["coverage"] != null) {
+        for (int hit in range["coverage"]["hits"]) {
+          hits.add(hit);
+        }
+      }
+    }
+  }
+  return hits;
+}
+
+main(args) => runIsolateTests(args, tests, testeeConcurrent: testFunction);
diff --git a/runtime/observatory/tests/service/get_source_report_test.dart b/runtime/observatory/tests/service/get_source_report_test.dart
index adea481..a1d5206 100644
--- a/runtime/observatory/tests/service/get_source_report_test.dart
+++ b/runtime/observatory/tests/service/get_source_report_test.dart
@@ -91,7 +91,7 @@
     final numRanges = coverage['ranges'].length;
     expect(coverage['type'], equals('SourceReport'));
 
-    expect(numRanges, equals(10));
+    expect(numRanges, equals(11));
     expect(coverage['ranges'][0], equals(expectedRange));
     expect(coverage['scripts'].length, 1);
     expect(
@@ -106,7 +106,7 @@
     };
     coverage = await isolate.invokeRpcNoUpgrade('getSourceReport', params);
     expect(coverage['type'], equals('SourceReport'));
-    expect(coverage['ranges'].length, 11);
+    expect(coverage['ranges'].length, 12);
     expect(allRangesCompiled(coverage), isTrue);
 
     // One function
diff --git a/runtime/observatory/tests/service/service_kernel.status b/runtime/observatory/tests/service/service_kernel.status
index b62a6e9..59145d2 100644
--- a/runtime/observatory/tests/service/service_kernel.status
+++ b/runtime/observatory/tests/service/service_kernel.status
@@ -109,6 +109,7 @@
 get_allocation_samples_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_isolate_after_language_error_test: CompileTimeError
 get_object_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
+get_source_report_const_coverage_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_source_report_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_source_report_with_mixin_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_stack_limit_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
diff --git a/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_lib.dart b/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_lib.dart
new file mode 100644
index 0000000..b0f078d
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_lib.dart
@@ -0,0 +1,12 @@
+// 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.
+
+import "get_source_report_const_coverage_test.dart";
+
+void testFunction() {
+  const namedFoo = Foo.named3();
+  const namedFoo2 = Foo.named3();
+  const namedIdentical = identical(namedFoo, namedFoo2);
+  print("namedIdentical: $namedIdentical");
+}
diff --git a/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_test.dart b/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_test.dart
new file mode 100644
index 0000000..7c6af16
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_test.dart
@@ -0,0 +1,129 @@
+// 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.
+
+import 'package:observatory_2/service_io.dart';
+import 'package:test/test.dart';
+import 'test_helper.dart';
+import 'service_test_common.dart';
+import 'dart:developer';
+
+import 'get_source_report_const_coverage_lib.dart' as lib;
+
+const String filename = "get_source_report_const_coverage_test";
+const Set<int> expectedLinesHit = {20, 22, 26};
+const Set<int> expectedLinesNotHit = {24};
+
+class Foo {
+  final int x;
+  // Expect this constructor to be coverage by coverage.
+  const Foo([int x]) : this.x = x ?? 42;
+  // Expect this constructor to be coverage by coverage too.
+  const Foo.named1([int x]) : this.x = x ?? 42;
+  // Expect this constructor to *NOT* be coverage by coverage.
+  const Foo.named2([int x]) : this.x = x ?? 42;
+  // Expect this constructor to be coverage by coverage too (from lib).
+  const Foo.named3([int x]) : this.x = x ?? 42;
+}
+
+void testFunction() {
+  const foo = Foo();
+  const foo2 = Foo();
+  const fooIdentical = identical(foo, foo2);
+  print(fooIdentical);
+
+  const namedFoo = Foo.named1();
+  const namedFoo2 = Foo.named1();
+  const namedIdentical = identical(namedFoo, namedFoo2);
+  print(fooIdentical);
+
+  debugger();
+
+  // That this is called after (or at all) is not relevent for the code
+  // coverage of constants.
+  lib.testFunction();
+
+  print("Done");
+}
+
+var tests = <IsolateTest>[
+  hasStoppedAtBreakpoint,
+  (Isolate isolate) async {
+    final stack = await isolate.getStack();
+
+    // Make sure we are in the right place.
+    expect(stack.type, equals('Stack'));
+    expect(stack['frames'].length, greaterThanOrEqualTo(1));
+    expect(stack['frames'][0].function.name, equals('testFunction'));
+
+    final List<Script> scripts = await isolate.getScripts();
+    Script foundScript;
+    for (Script script in scripts) {
+      if (script.uri.contains(filename)) {
+        foundScript = script;
+        break;
+      }
+    }
+
+    Set<int> hits;
+    {
+      // Get report for everything; then collect for this library.
+      final Map<String, Object> params = {
+        'reports': ['Coverage'],
+      };
+      final coverage =
+          await isolate.invokeRpcNoUpgrade('getSourceReport', params);
+      hits = getHitsFor(coverage, filename);
+      await foundScript.load();
+      final Set<int> lines = {};
+      for (int hit in hits) {
+        // We expect every hit to be translatable to line
+        // (i.e. tokenToLine to return non-null).
+        lines.add(foundScript.tokenToLine(hit));
+      }
+      print("Token position hits: $hits --- line hits: $lines");
+      expect(lines.intersection(expectedLinesHit), equals(expectedLinesHit));
+      expect(lines.intersection(expectedLinesNotHit), isEmpty);
+    }
+    {
+      // Now get report for the this file only.
+      final Map<String, Object> params = {
+        'reports': ['Coverage'],
+        'scriptId': foundScript.id
+      };
+      final coverage =
+          await isolate.invokeRpcNoUpgrade('getSourceReport', params);
+      final Set<int> localHits = getHitsFor(coverage, filename);
+      expect(localHits.length, equals(hits.length));
+      expect(hits.toList()..sort(), equals(localHits.toList()..sort()));
+      print(localHits);
+    }
+  },
+];
+
+Set<int> getHitsFor(Map coverage, String uriContains) {
+  final List scripts = coverage["scripts"];
+  final Set<int> scriptIdsWanted = {};
+  for (int i = 0; i < scripts.length; i++) {
+    final Map script = scripts[i];
+    final String scriptUri = script["uri"];
+    if (scriptUri.contains(uriContains)) {
+      scriptIdsWanted.add(i);
+    }
+  }
+  final List ranges = coverage["ranges"];
+  final Set<int> hits = {};
+  for (int i = 0; i < ranges.length; i++) {
+    final Map range = ranges[i];
+    if (scriptIdsWanted.contains(range["scriptIndex"])) {
+      if (range["coverage"] != null) {
+        for (int hit in range["coverage"]["hits"]) {
+          hits.add(hit);
+        }
+      }
+    }
+  }
+  return hits;
+}
+
+main(args) => runIsolateTests(args, tests, testeeConcurrent: testFunction);
diff --git a/runtime/observatory_2/tests/service_2/get_source_report_test.dart b/runtime/observatory_2/tests/service_2/get_source_report_test.dart
index 97606de..78ea471 100644
--- a/runtime/observatory_2/tests/service_2/get_source_report_test.dart
+++ b/runtime/observatory_2/tests/service_2/get_source_report_test.dart
@@ -91,7 +91,7 @@
     final numRanges = coverage['ranges'].length;
     expect(coverage['type'], equals('SourceReport'));
 
-    expect(numRanges, equals(10));
+    expect(numRanges, equals(11));
     expect(coverage['ranges'][0], equals(expectedRange));
     expect(coverage['scripts'].length, 1);
     expect(
@@ -106,7 +106,7 @@
     };
     coverage = await isolate.invokeRpcNoUpgrade('getSourceReport', params);
     expect(coverage['type'], equals('SourceReport'));
-    expect(coverage['ranges'].length, 11);
+    expect(coverage['ranges'].length, 12);
     expect(allRangesCompiled(coverage), isTrue);
 
     // One function
diff --git a/runtime/observatory_2/tests/service_2/service_2_kernel.status b/runtime/observatory_2/tests/service_2/service_2_kernel.status
index 2b6e18b..5522831 100644
--- a/runtime/observatory_2/tests/service_2/service_2_kernel.status
+++ b/runtime/observatory_2/tests/service_2/service_2_kernel.status
@@ -109,6 +109,7 @@
 get_allocation_samples_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_isolate_after_language_error_test: CompileTimeError
 get_object_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
+get_source_report_const_coverage_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_source_report_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_source_report_with_mixin_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_stack_limit_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.cc b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
index a90b40d..794f917 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.cc
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
@@ -2807,6 +2807,33 @@
   return H.DartString(reader_.BufferAt(ReaderOffset()), size, Heap::kOld);
 }
 
+ExternalTypedDataPtr KernelReaderHelper::GetConstantCoverageFor(
+    intptr_t index) {
+  AlternativeReadingScope alt(&reader_);
+  SetOffset(GetOffsetForSourceInfo(index));
+  SkipBytes(ReadUInt());                         // skip uri.
+  SkipBytes(ReadUInt());                         // skip source.
+  const intptr_t line_start_count = ReadUInt();  // read number of line start
+                                                 // entries.
+  for (intptr_t i = 0; i < line_start_count; ++i) {
+    ReadUInt();
+  }
+
+  SkipBytes(ReadUInt());  // skip import uri.
+
+  intptr_t start_offset = ReaderOffset();
+
+  // Read past "constant coverage constructors".
+  const intptr_t constant_coverage_constructors = ReadUInt();
+  for (intptr_t i = 0; i < constant_coverage_constructors; ++i) {
+    ReadUInt();
+  }
+
+  intptr_t end_offset = ReaderOffset();
+
+  return reader_.ExternalDataFromTo(start_offset, end_offset);
+}
+
 intptr_t ActiveClass::MemberTypeParameterCount(Zone* zone) {
   ASSERT(member != NULL);
   if (member->IsFactory()) {
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.h b/runtime/vm/compiler/frontend/kernel_translation_helper.h
index 0bb8141..b60884c2 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.h
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.h
@@ -1257,6 +1257,7 @@
   const String& GetSourceFor(intptr_t index);
   TypedDataPtr GetLineStartsFor(intptr_t index);
   String& SourceTableImportUriFor(intptr_t index, uint32_t binaryVersion);
+  ExternalTypedDataPtr GetConstantCoverageFor(intptr_t index);
 
   Zone* zone_;
   TranslationHelper& translation_helper_;
@@ -1295,6 +1296,8 @@
   friend class ObfuscationProhibitionsMetadataHelper;
   friend class LoadingUnitsMetadataHelper;
   friend bool NeedsDynamicInvocationForwarder(const Function& function);
+  friend ArrayPtr CollectConstConstructorCoverageFrom(
+      const Script& interesting_script);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(KernelReaderHelper);
diff --git a/runtime/vm/compiler/jit/compiler.cc b/runtime/vm/compiler/jit/compiler.cc
index 2c54ec6..3e142f4 100644
--- a/runtime/vm/compiler/jit/compiler.cc
+++ b/runtime/vm/compiler/jit/compiler.cc
@@ -211,17 +211,15 @@
 DEFINE_RUNTIME_ENTRY(CompileFunction, 1) {
   ASSERT(thread->IsMutatorThread());
   const Function& function = Function::CheckedHandle(zone, arguments.ArgAt(0));
-  Object& result = Object::Handle(zone);
-  ASSERT(!function.HasCode());
 
-  result = Compiler::CompileFunction(thread, function);
-  if (result.IsError()) {
-    if (result.IsLanguageError()) {
-      Exceptions::ThrowCompileTimeError(LanguageError::Cast(result));
-      UNREACHABLE();
-    }
-    Exceptions::PropagateError(Error::Cast(result));
-  }
+  // In single-isolate scenarios the lazy compile stub is only invoked if
+  // there's no existing code. In multi-isolate scenarios with shared JITed code
+  // we can end up in the lazy compile runtime entry here with code being
+  // installed.
+  ASSERT(!function.HasCode() || FLAG_enable_isolate_groups);
+
+  // Will throw if compilation failed (e.g. with compile-time error).
+  function.EnsureHasCode();
 }
 
 bool Compiler::CanOptimizeFunction(Thread* thread, const Function& function) {
diff --git a/runtime/vm/compiler/runtime_offsets_extracted.h b/runtime/vm/compiler/runtime_offsets_extracted.h
index b2ff3da..63c13d4 100644
--- a/runtime/vm/compiler/runtime_offsets_extracted.h
+++ b/runtime/vm/compiler/runtime_offsets_extracted.h
@@ -503,7 +503,7 @@
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 12;
 static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 20;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 60;
-static constexpr dart::compiler::target::word Script_InstanceSize = 56;
+static constexpr dart::compiler::target::word Script_InstanceSize = 64;
 static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
 static constexpr dart::compiler::target::word SignatureData_InstanceSize = 12;
 static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -1026,7 +1026,7 @@
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 24;
 static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 40;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word Script_InstanceSize = 104;
 static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
 static constexpr dart::compiler::target::word SignatureData_InstanceSize = 24;
 static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -1540,7 +1540,7 @@
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 12;
 static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 20;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 60;
-static constexpr dart::compiler::target::word Script_InstanceSize = 56;
+static constexpr dart::compiler::target::word Script_InstanceSize = 64;
 static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
 static constexpr dart::compiler::target::word SignatureData_InstanceSize = 12;
 static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -2064,7 +2064,7 @@
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 24;
 static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 40;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word Script_InstanceSize = 104;
 static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
 static constexpr dart::compiler::target::word SignatureData_InstanceSize = 24;
 static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -3094,7 +3094,7 @@
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 24;
 static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 24;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word Script_InstanceSize = 88;
 static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
 static constexpr dart::compiler::target::word SignatureData_InstanceSize = 24;
 static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -4120,7 +4120,7 @@
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 24;
 static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 24;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word Script_InstanceSize = 88;
 static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
 static constexpr dart::compiler::target::word SignatureData_InstanceSize = 24;
 static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -5266,7 +5266,7 @@
 static constexpr dart::compiler::target::word AOT_Pointer_InstanceSize = 24;
 static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 40;
 static constexpr dart::compiler::target::word AOT_RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 88;
 static constexpr dart::compiler::target::word AOT_SendPort_InstanceSize = 24;
 static constexpr dart::compiler::target::word AOT_SignatureData_InstanceSize =
     24;
@@ -5847,7 +5847,7 @@
 static constexpr dart::compiler::target::word AOT_Pointer_InstanceSize = 24;
 static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 40;
 static constexpr dart::compiler::target::word AOT_RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 88;
 static constexpr dart::compiler::target::word AOT_SendPort_InstanceSize = 24;
 static constexpr dart::compiler::target::word AOT_SignatureData_InstanceSize =
     24;
@@ -6985,7 +6985,7 @@
 static constexpr dart::compiler::target::word AOT_Pointer_InstanceSize = 24;
 static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 24;
 static constexpr dart::compiler::target::word AOT_RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 88;
 static constexpr dart::compiler::target::word AOT_SendPort_InstanceSize = 24;
 static constexpr dart::compiler::target::word AOT_SignatureData_InstanceSize =
     24;
@@ -7559,7 +7559,7 @@
 static constexpr dart::compiler::target::word AOT_Pointer_InstanceSize = 24;
 static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 24;
 static constexpr dart::compiler::target::word AOT_RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 88;
 static constexpr dart::compiler::target::word AOT_SendPort_InstanceSize = 24;
 static constexpr dart::compiler::target::word AOT_SignatureData_InstanceSize =
     24;
diff --git a/runtime/vm/kernel.cc b/runtime/vm/kernel.cc
index 952e41a..ed49742 100644
--- a/runtime/vm/kernel.cc
+++ b/runtime/vm/kernel.cc
@@ -347,6 +347,36 @@
   script.set_debug_positions(array_object);
 }
 
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+ArrayPtr CollectConstConstructorCoverageFrom(const Script& interesting_script) {
+  Thread* thread = Thread::Current();
+  Zone* zone = thread->zone();
+  interesting_script.LookupSourceAndLineStarts(zone);
+  TranslationHelper helper(thread);
+  helper.InitFromScript(interesting_script);
+
+  ExternalTypedData& data =
+      ExternalTypedData::Handle(zone, interesting_script.constant_coverage());
+
+  KernelReaderHelper kernel_reader(zone, &helper, interesting_script, data, 0);
+
+  // Read "constant coverage constructors".
+  const intptr_t constant_coverage_constructors = kernel_reader.ReadUInt();
+  const Array& constructors =
+      Array::Handle(Array::New(constant_coverage_constructors));
+  for (intptr_t i = 0; i < constant_coverage_constructors; ++i) {
+    NameIndex kernel_name = kernel_reader.ReadCanonicalNameReference();
+    Class& klass = Class::ZoneHandle(
+        zone,
+        helper.LookupClassByKernelClass(helper.EnclosingName(kernel_name)));
+    const Function& target = Function::ZoneHandle(
+        zone, helper.LookupConstructorByKernelConstructor(klass, kernel_name));
+    constructors.SetAt(i, target);
+  }
+  return constructors.raw();
+}
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+
 ObjectPtr EvaluateStaticConstFieldInitializer(const Field& field) {
   ASSERT(field.is_static() && field.is_const());
 
diff --git a/runtime/vm/kernel.h b/runtime/vm/kernel.h
index ed66e25..79c5670 100644
--- a/runtime/vm/kernel.h
+++ b/runtime/vm/kernel.h
@@ -195,6 +195,10 @@
 
 void CollectTokenPositionsFor(const Script& script);
 
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+ArrayPtr CollectConstConstructorCoverageFrom(const Script& interesting_script);
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+
 ObjectPtr EvaluateStaticConstFieldInitializer(const Field& field);
 ObjectPtr EvaluateMetadata(const Library& library,
                            intptr_t kernel_offset,
diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc
index 4dbcfb6..878582b 100644
--- a/runtime/vm/kernel_loader.cc
+++ b/runtime/vm/kernel_loader.cc
@@ -2093,6 +2093,10 @@
   const String& uri_string = helper_.SourceTableUriFor(index);
   const String& import_uri_string =
       helper_.SourceTableImportUriFor(index, program_->binary_version());
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+  ExternalTypedData& constant_coverage =
+      ExternalTypedData::Handle(Z, helper_.GetConstantCoverageFor(index));
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
 
   String& sources = String::Handle(Z);
   TypedData& line_starts = TypedData::Handle(Z);
@@ -2138,6 +2142,9 @@
   script.set_kernel_script_index(index);
   script.set_kernel_program_info(kernel_program_info_);
   script.set_line_starts(line_starts);
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+  script.set_constant_coverage(constant_coverage);
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
   script.set_debug_positions(Array::null_array());
   return script.raw();
 }
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 927cfef..1347673 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -11161,6 +11161,16 @@
   raw_ptr()->set_line_starts(value.raw());
 }
 
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+void Script::set_constant_coverage(const ExternalTypedData& value) const {
+  raw_ptr()->set_constant_coverage(value.raw());
+}
+
+ExternalTypedDataPtr Script::constant_coverage() const {
+  return raw_ptr()->constant_coverage();
+}
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+
 void Script::set_debug_positions(const Array& value) const {
   raw_ptr()->set_debug_positions(value.raw());
 }
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 8ea8ae0..960f3f1 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -4522,6 +4522,12 @@
 
   TypedDataPtr line_starts() const;
 
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+  ExternalTypedDataPtr constant_coverage() const;
+
+  void set_constant_coverage(const ExternalTypedData& value) const;
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+
   void set_line_starts(const TypedData& value) const;
 
   void set_debug_positions(const Array& value) const;
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index b6e04fc..15af026 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -1315,7 +1315,7 @@
   friend class StoreInstanceFieldInstr;  // For sizeof(guarded_cid_/...)
 };
 
-class ScriptLayout : public ObjectLayout {
+class alignas(8) ScriptLayout : public ObjectLayout {
  public:
   enum {
     kLazyLookupSourceAndLineStartsPos = 0,
@@ -1330,6 +1330,9 @@
   POINTER_FIELD(StringPtr, resolved_url)
   POINTER_FIELD(ArrayPtr, compile_time_constants)
   POINTER_FIELD(TypedDataPtr, line_starts)
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+  POINTER_FIELD(ExternalTypedDataPtr, constant_coverage)
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
   POINTER_FIELD(ArrayPtr, debug_positions)
   POINTER_FIELD(KernelProgramInfoPtr, kernel_program_info)
   POINTER_FIELD(StringPtr, source)
@@ -1361,8 +1364,13 @@
                kLazyLookupSourceAndLineStartsSize>;
   uint8_t flags_;
 
-  intptr_t kernel_script_index_;
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
   int64_t load_timestamp_;
+  int32_t kernel_script_index_;
+#else
+  int32_t kernel_script_index_;
+  int64_t load_timestamp_;
+#endif
 };
 
 class LibraryLayout : public ObjectLayout {
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 0a13543..23f05e8 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -1153,9 +1153,15 @@
   return result.raw();
 }
 
-static void TrySwitchInstanceCall(const ICData& ic_data,
-                                  const Function& target_function) {
 #if !defined(DART_PRECOMPILED_RUNTIME)
+static void TrySwitchInstanceCall(Thread* thread,
+                                  StackFrame* caller_frame,
+                                  const Code& caller_code,
+                                  const Function& caller_function,
+                                  const ICData& ic_data,
+                                  const Function& target_function) {
+  auto zone = thread->zone();
+
   // Monomorphic/megamorphic calls only check the receiver CID.
   if (ic_data.NumArgsTested() != 1) return;
 
@@ -1169,24 +1175,14 @@
   if (Isolate::Current()->has_attempted_stepping()) return;
 #endif
 
-  Thread* thread = Thread::Current();
-  DartFrameIterator iterator(thread,
-                             StackFrameIterator::kNoCrossThreadIteration);
-  StackFrame* caller_frame = iterator.NextFrame();
-  ASSERT(caller_frame->IsDartFrame());
-
   // Monomorphic/megamorphic calls are only for unoptimized code.
-  Zone* zone = thread->zone();
-  const Code& caller_code = Code::Handle(zone, caller_frame->LookupDartCode());
-  if (caller_code.is_optimized()) return;
+  ASSERT(!caller_code.is_optimized());
 
   // Code is detached from its function. This will prevent us from resetting
   // the switchable call later because resets are function based and because
   // the ic_data_array belongs to the function instead of the code. This should
   // only happen because of reload, but it sometimes happens with KBC mixed mode
   // probably through a race between foreground and background compilation.
-  const Function& caller_function =
-      Function::Handle(zone, caller_code.function());
   if (caller_function.unoptimized_code() != caller_code.raw()) {
     return;
   }
@@ -1237,9 +1233,8 @@
     }
     return;  // Success.
   }
-
-#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 }
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
 // Perform the subtype and return constant function based on the result.
 static FunctionPtr ComputeTypeCheckTarget(const Instance& receiver,
@@ -1255,151 +1250,38 @@
   return target.raw();
 }
 
-static FunctionPtr InlineCacheMissHandlerGivenTargetFunction(
-    const GrowableArray<const Instance*>& args,  // Checked arguments only.
-    const ICData& ic_data,
-    intptr_t count,
-    const Function& target_function) {
-  if (target_function.IsNull()) {
-    return target_function.raw();
-  }
+static FunctionPtr Resolve(
+    Thread* thread,
+    Zone* zone,
+    const GrowableArray<const Instance*>& caller_arguments,
+    const Class& receiver_class,
+    const String& name,
+    const Array& descriptor) {
+  ASSERT(name.IsSymbol());
+  auto& target_function = Function::Handle(zone);
+  ArgumentsDescriptor args_desc(descriptor);
 
-  const Instance& receiver = *args[0];
-
-  if (args.length() == 1) {
-    auto exactness = StaticTypeExactnessState::NotTracking();
-    if (ic_data.is_tracking_exactness()) {
-#if !defined(DART_PRECOMPILED_RUNTIME)
-      exactness = receiver.IsNull() ? StaticTypeExactnessState::NotExact()
-                                    : StaticTypeExactnessState::Compute(
-                                          Type::Cast(AbstractType::Handle(
-                                              ic_data.receivers_static_type())),
-                                          receiver);
-#else
-      UNREACHABLE();
-#endif
-    }
-    ic_data.EnsureHasReceiverCheck(receiver.GetClassId(), target_function,
-                                   count, exactness);
-  } else {
-    GrowableArray<intptr_t> class_ids(args.length());
-    ASSERT(ic_data.NumArgsTested() == args.length());
-    for (intptr_t i = 0; i < args.length(); i++) {
-      class_ids.Add(args[i]->GetClassId());
-    }
-    ic_data.EnsureHasCheck(class_ids, target_function, count);
-  }
-  if (FLAG_trace_ic_miss_in_optimized || FLAG_trace_ic) {
-    DartFrameIterator iterator(Thread::Current(),
-                               StackFrameIterator::kNoCrossThreadIteration);
-    StackFrame* caller_frame = iterator.NextFrame();
-    ASSERT(caller_frame != NULL);
-    if (FLAG_trace_ic_miss_in_optimized) {
-      const Code& caller = Code::Handle(Code::LookupCode(caller_frame->pc()));
-      if (caller.is_optimized()) {
-        OS::PrintErr("IC miss in optimized code; call %s -> %s\n",
-                     Function::Handle(caller.function()).ToCString(),
-                     target_function.ToCString());
-      }
-    }
-    if (FLAG_trace_ic) {
-      OS::PrintErr("InlineCacheMissHandler %" Pd " call at %#" Px
-                   "' "
-                   "adding <%s> id:%" Pd " -> <%s>\n",
-                   args.length(), caller_frame->pc(),
-                   Class::Handle(receiver.clazz()).ToCString(),
-                   receiver.GetClassId(), target_function.ToCString());
-    }
-  }
-
-  TrySwitchInstanceCall(ic_data, target_function);
-
-  return target_function.raw();
-}
-
-static FunctionPtr InlineCacheMissHandler(
-    const GrowableArray<const Instance*>& args,  // Checked arguments only.
-    const ICData& ic_data) {
-  Thread* thread = Thread::Current();
-  Zone* zone = thread->zone();
-
-  const Instance& receiver = *args[0];
-  ArgumentsDescriptor arguments_descriptor(
-      Array::Handle(zone, ic_data.arguments_descriptor()));
-  String& function_name = String::Handle(zone, ic_data.target_name());
-  ASSERT(function_name.IsSymbol());
-
-  const Class& receiver_class = Class::Handle(zone, receiver.clazz());
-  Function& target_function = Function::Handle(zone);
   if (receiver_class.EnsureIsFinalized(thread) == Error::null()) {
-    target_function = Resolver::ResolveDynamicForReceiverClass(
-        receiver_class, function_name, arguments_descriptor);
+    target_function = Resolver::ResolveDynamicForReceiverClass(receiver_class,
+                                                               name, args_desc);
+  }
+  if (caller_arguments.length() == 2 &&
+      target_function.raw() ==
+          thread->isolate()->object_store()->simple_instance_of_function()) {
+    // Replace the target function with constant function.
+    const AbstractType& type = AbstractType::Cast(*caller_arguments[1]);
+    target_function =
+        ComputeTypeCheckTarget(*caller_arguments[0], type, args_desc);
   }
 
-  ObjectStore* store = thread->isolate()->object_store();
-  if (target_function.raw() == store->simple_instance_of_function()) {
-    // Replace the target function with constant function.
-    ASSERT(args.length() == 2);
-    const AbstractType& type = AbstractType::Cast(*args[1]);
-    target_function =
-        ComputeTypeCheckTarget(receiver, type, arguments_descriptor);
-  }
   if (target_function.IsNull()) {
-    if (FLAG_trace_ic) {
-      OS::PrintErr("InlineCacheMissHandler NULL function for %s receiver: %s\n",
-                   String::Handle(zone, ic_data.target_name()).ToCString(),
-                   receiver.ToCString());
-    }
-    const Array& args_descriptor =
-        Array::Handle(zone, ic_data.arguments_descriptor());
-    const String& target_name = String::Handle(zone, ic_data.target_name());
-    target_function =
-        InlineCacheMissHelper(receiver_class, args_descriptor, target_name);
+    target_function = InlineCacheMissHelper(receiver_class, descriptor, name);
   }
   if (target_function.IsNull()) {
     ASSERT(!FLAG_lazy_dispatchers);
-    return target_function.raw();
   }
 
-  SafepointMutexLocker ml(thread->isolate_group()->patchable_call_mutex());
-
-  return InlineCacheMissHandlerGivenTargetFunction(args, ic_data, 1,
-                                                   target_function);
-}
-
-// Handles inline cache misses by updating the IC data array of the call site.
-//   Arg0: Receiver object.
-//   Arg1: IC data object.
-//   Returns: target function with compiled code or null.
-// Modifies the instance call to hold the updated IC data array.
-DEFINE_RUNTIME_ENTRY(InlineCacheMissHandlerOneArg, 2) {
-  const Instance& receiver = Instance::CheckedHandle(zone, arguments.ArgAt(0));
-  const ICData& ic_data = ICData::CheckedHandle(zone, arguments.ArgAt(1));
-  RELEASE_ASSERT(!FLAG_precompiled_mode);
-  GrowableArray<const Instance*> args(1);
-  args.Add(&receiver);
-  const Function& result =
-      Function::Handle(zone, InlineCacheMissHandler(args, ic_data));
-  arguments.SetReturn(result);
-}
-
-// Handles inline cache misses by updating the IC data array of the call site.
-//   Arg0: Receiver object.
-//   Arg1: Argument after receiver.
-//   Arg2: IC data object.
-//   Returns: target function with compiled code or null.
-// Modifies the instance call to hold the updated IC data array.
-DEFINE_RUNTIME_ENTRY(InlineCacheMissHandlerTwoArgs, 3) {
-  const Instance& receiver = Instance::CheckedHandle(zone, arguments.ArgAt(0));
-  const Instance& other = Instance::CheckedHandle(zone, arguments.ArgAt(1));
-  const ICData& ic_data = ICData::CheckedHandle(zone, arguments.ArgAt(2));
-  RELEASE_ASSERT(!FLAG_precompiled_mode);
-  GrowableArray<const Instance*> args(2);
-  args.Add(&receiver);
-  args.Add(&other);
-  const Function& result =
-      Function::Handle(zone, InlineCacheMissHandler(args, ic_data));
-  arguments.SetReturn(result);
+  return target_function.raw();
 }
 
 // Handles a static call in unoptimized code that has one argument type not
@@ -1554,26 +1436,39 @@
 
 #endif  // defined(DART_PRECOMPILED_RUNTIME)
 
-// We use two different instance calls in JIT vs AOT but both are patchable. A
-// subset of the patching cases are handled by [PatchableCallHandler].
+enum class MissHandler {
+  kInlineCacheMiss,
+  kSwitchableCallMiss,
+};
+
+// Handles updating of type feedback and possible patching of instance calls.
+//
+// It works in 3 separate steps:
+//   - resolve the actual target
+//   - update type feedback & (optionally) perform call site transition
+//   - return the right values
+//
+// Depending on the JIT/AOT mode we obtain current and patch new (target, data)
+// differently:
 //
 //   - JIT calls must be patched with CodePatcher::PatchInstanceCallAt()
 //   - AOT calls must be patched with CodePatcher::PatchSwitchableCallAt()
 //
-// In the JIT it is possible that a call site will use two different miss
-// handlers:
+// Independent of which miss handler was used or how we will return, we look at
+// current (target, data) and see if we need to transition the call site to a
+// new (target, data). We do this while holding `IG->patchable_call_mutex()`.
 //
-//   - InlineCacheMissHandlerOneArg (which patches callsite to be monomorphic)
-//   - SwitchableCallMiss (which patches callsite to be polymorphic)
-//   - InlineCacheMissHandlerOneArg (which patches it to be megamorphic)
-//   - SwitchableCallMiss (which updates megamorphic cache)
+// Depending on which miss handler got called we might need to return
+// differently:
 //
-// Once we share JITed code across isolates there are more possible sequences
-// which we need to consider.
+//   - SwitchableCallMiss will get get (stub, data) return value
+//   - InlineCache*Miss will get get function as return value
+//
 class PatchableCallHandler {
  public:
   PatchableCallHandler(Thread* thread,
-                       const Instance& receiver,
+                       const GrowableArray<const Instance*>& caller_arguments,
+                       MissHandler miss_handler,
                        NativeArguments arguments,
                        StackFrame* caller_frame,
                        const Code& caller_code,
@@ -1581,20 +1476,26 @@
       : isolate_(thread->isolate()),
         thread_(thread),
         zone_(thread->zone()),
-        receiver_(receiver),
+        caller_arguments_(caller_arguments),
+        miss_handler_(miss_handler),
         arguments_(arguments),
         caller_frame_(caller_frame),
         caller_code_(caller_code),
         caller_function_(caller_function),
         name_(String::Handle()),
-        args_descriptor_(Array::Handle()) {}
+        args_descriptor_(Array::Handle()) {
+    // We only have two arg IC calls in JIT mode.
+    ASSERT(caller_arguments_.length() == 1 || !FLAG_precompiled_mode);
+  }
 
+  void ResolveSwitchAndReturn(const Object& data);
+
+ private:
   FunctionPtr ResolveTargetFunction(const Object& data);
   void HandleMiss(const Object& old_data,
                   const Code& old_target,
                   const Function& target_function);
 
- private:
 #if defined(DART_PRECOMPILED_RUNTIME)
   void DoUnlinkedCallAOT(const UnlinkedCall& unlinked,
                          const Function& target_function);
@@ -1611,17 +1512,48 @@
 #else
   void DoMonomorphicMissJIT(const Object& data,
                             const Function& target_function);
+  void DoICDataMissJIT(const ICData& data,
+                       const Object& old_data,
+                       const Function& target_function);
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
   void DoMegamorphicMiss(const MegamorphicCache& data,
                          const Function& target_function);
 
+  void UpdateICDataWithTarget(const ICData& ic_data,
+                              const Function& target_function);
+  void TrySwitch(const ICData& ic_data, const Function& target_function);
+
+  void ReturnAOT(const Code& stub, const Object& data);
+  void ReturnJIT(const Code& stub, const Object& data, const Function& target);
+  void ReturnJITorAOT(const Code& stub,
+                      const Object& data,
+                      const Function& target);
+
+  const Instance& receiver() { return *caller_arguments_[0]; }
+
+  bool should_consider_patching() {
+    // In AOT we use switchable calls.
+    if (FLAG_precompiled_mode) return true;
+
+    // In JIT instance calls use a different calling sequence in unoptimized vs
+    // optimized code (see [FlowGraphCompiler::EmitInstanceCallJIT] vs
+    // [FlowGraphCompiler::EmitOptimizedInstanceCall]).
+    //
+    // The [CodePatcher::GetInstanceCallAt], [CodePatcher::PatchInstanceCallAt]
+    // only recognize unoptimized call pattern.
+    //
+    // So we will not try to switch optimized instance calls.
+    return !caller_code_.is_optimized();
+  }
+
   ICDataPtr NewICData();
   ICDataPtr NewICDataWithTarget(intptr_t cid, const Function& target);
 
   Isolate* isolate_;
   Thread* thread_;
   Zone* zone_;
-  const Instance& receiver_;
+  const GrowableArray<const Instance*>& caller_arguments_;
+  MissHandler miss_handler_;
   NativeArguments arguments_;
   StackFrame* caller_frame_;
   const Code& caller_code_;
@@ -1640,7 +1572,7 @@
       zone_,
       target_function.IsNull()
           ? NewICData()
-          : NewICDataWithTarget(receiver_.GetClassId(), target_function));
+          : NewICDataWithTarget(receiver().GetClassId(), target_function));
 
   Object& object = Object::Handle(zone_, ic_data.raw());
   Code& code = Code::Handle(zone_, StubCode::ICCallThroughCode().raw());
@@ -1659,7 +1591,7 @@
     const Code& target_code =
         Code::Handle(zone_, target_function.CurrentCode());
     const Smi& expected_cid =
-        Smi::Handle(zone_, Smi::New(receiver_.GetClassId()));
+        Smi::Handle(zone_, Smi::New(receiver().GetClassId()));
 
     if (unlinked.can_patch_to_monomorphic()) {
       object = expected_cid.raw();
@@ -1675,8 +1607,7 @@
 
   // Return the ICData. The miss stub will jump to continue in the IC lookup
   // stub.
-  arguments_.SetArgAt(0, StubCode::ICCallThroughCode());
-  arguments_.SetReturn(ic_data);
+  ReturnAOT(StubCode::ICCallThroughCode(), ic_data);
 }
 
 bool PatchableCallHandler::CanExtendSingleTargetRange(
@@ -1689,14 +1620,14 @@
     return false;
   }
   intptr_t unchecked_lower, unchecked_upper;
-  if (receiver_.GetClassId() < *lower) {
-    unchecked_lower = receiver_.GetClassId();
+  if (receiver().GetClassId() < *lower) {
+    unchecked_lower = receiver().GetClassId();
     unchecked_upper = *lower - 1;
-    *lower = receiver_.GetClassId();
+    *lower = receiver().GetClassId();
   } else {
-    unchecked_upper = receiver_.GetClassId();
+    unchecked_upper = receiver().GetClassId();
     unchecked_lower = *upper + 1;
-    *upper = receiver_.GetClassId();
+    *upper = receiver().GetClassId();
   }
 
   return IsSingleTarget(isolate_, zone_, unchecked_lower, unchecked_upper,
@@ -1724,30 +1655,6 @@
 }
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
-static FunctionPtr Resolve(Thread* thread,
-                           Zone* zone,
-                           const Class& receiver_class,
-                           const String& name,
-                           const Array& descriptor) {
-  ASSERT(name.IsSymbol());
-  Function& target_function = Function::Handle(zone);
-
-  if (receiver_class.EnsureIsFinalized(thread) == Error::null()) {
-    ArgumentsDescriptor args_desc(descriptor);
-    target_function = Resolver::ResolveDynamicForReceiverClass(receiver_class,
-                                                               name, args_desc);
-  }
-
-  if (target_function.IsNull()) {
-    target_function = InlineCacheMissHelper(receiver_class, descriptor, name);
-    if (target_function.IsNull()) {
-      ASSERT(!FLAG_lazy_dispatchers);
-    }
-  }
-
-  return target_function.raw();
-}
-
 #if defined(DART_PRECOMPILED_RUNTIME)
 void PatchableCallHandler::DoMonomorphicMissAOT(
     const Object& data,
@@ -1759,12 +1666,12 @@
     RELEASE_ASSERT(data.IsMonomorphicSmiableCall());
     old_expected_cid = MonomorphicSmiableCall::Cast(data).expected_cid();
   }
-  const bool is_monomorphic_hit = old_expected_cid == receiver_.GetClassId();
+  const bool is_monomorphic_hit = old_expected_cid == receiver().GetClassId();
   const auto& old_receiver_class =
       Class::Handle(zone_, isolate_->class_table()->At(old_expected_cid));
   const auto& old_target = Function::Handle(
-      zone_,
-      Resolve(thread_, zone_, old_receiver_class, name_, args_descriptor_));
+      zone_, Resolve(thread_, zone_, caller_arguments_, old_receiver_class,
+                     name_, args_descriptor_));
 
   const auto& ic_data = ICData::Handle(
       zone_, old_target.IsNull()
@@ -1774,8 +1681,7 @@
   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);
+    ReturnAOT(StubCode::ICCallThroughCode(), ic_data);
     return;
   }
 
@@ -1795,8 +1701,7 @@
                                        stub);
     // Return the ICData. The miss stub will jump to continue in the IC call
     // stub.
-    arguments_.SetArgAt(0, StubCode::ICCallThroughCode());
-    arguments_.SetReturn(ic_data);
+    ReturnAOT(StubCode::ICCallThroughCode(), ic_data);
     return;
   }
 
@@ -1807,8 +1712,7 @@
 
   // Return the ICData. The miss stub will jump to continue in the IC lookup
   // stub.
-  arguments_.SetArgAt(0, stub);
-  arguments_.SetReturn(ic_data);
+  ReturnAOT(stub, ic_data);
 }
 #endif  // defined(DART_PRECOMPILED_RUNTIME)
 
@@ -1833,15 +1737,12 @@
                  caller_frame_->pc(), ic_data.ToCString());
   }
 
-  // Don't count during insertion because the IC stub we continue through will
-  // do an increment.
-  GrowableArray<const Instance*> args(1);
-  args.Add(&receiver_);
-  InlineCacheMissHandlerGivenTargetFunction(args, ic_data, /*count=*/0,
-                                            target_function);
-
-  arguments_.SetArgAt(0, stub);
-  arguments_.SetReturn(ic_data);
+  ASSERT(caller_arguments_.length() == 1);
+  UpdateICDataWithTarget(ic_data, target_function);
+  ASSERT(should_consider_patching());
+  TrySwitchInstanceCall(thread_, caller_frame_, caller_code_, caller_function_,
+                        ic_data, target_function);
+  ReturnJIT(stub, ic_data, target_function);
 }
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
@@ -1858,7 +1759,7 @@
       zone_,
       target_function.IsNull()
           ? NewICData()
-          : NewICDataWithTarget(receiver_.GetClassId(), target_function));
+          : NewICDataWithTarget(receiver().GetClassId(), target_function));
 
   intptr_t lower = data.lower_limit();
   intptr_t upper = data.upper_limit();
@@ -1868,8 +1769,7 @@
     data.set_upper_limit(upper);
     // Return the ICData. The single target stub will jump to continue in the
     // IC call stub.
-    arguments_.SetArgAt(0, StubCode::ICCallThroughCode());
-    arguments_.SetReturn(ic_data);
+    ReturnAOT(StubCode::ICCallThroughCode(), ic_data);
     return;
   }
 
@@ -1880,8 +1780,7 @@
 
   // Return the ICData. The single target stub will jump to continue in the
   // IC call stub.
-  arguments_.SetArgAt(0, stub);
-  arguments_.SetReturn(ic_data);
+  ReturnAOT(stub, ic_data);
 }
 #endif  // defined(DART_PRECOMPILED_RUNTIME)
 
@@ -1889,7 +1788,7 @@
 void PatchableCallHandler::DoICDataMissAOT(const ICData& ic_data,
                                            const Function& target_function) {
   const String& name = String::Handle(zone_, ic_data.target_name());
-  const Class& cls = Class::Handle(zone_, receiver_.clazz());
+  const Class& cls = Class::Handle(zone_, receiver().clazz());
   ASSERT(!cls.IsNull());
   const Array& descriptor =
       Array::CheckedHandle(zone_, ic_data.arguments_descriptor());
@@ -1900,8 +1799,7 @@
   }
 
   if (target_function.IsNull()) {
-    arguments_.SetArgAt(0, StubCode::NoSuchMethodDispatcher());
-    arguments_.SetReturn(ic_data);
+    ReturnAOT(StubCode::NoSuchMethodDispatcher(), ic_data);
     return;
   }
 
@@ -1919,14 +1817,13 @@
     const Code& target_code =
         Code::Handle(zone_, target_function.EnsureHasCode());
     const Smi& expected_cid =
-        Smi::Handle(zone_, Smi::New(receiver_.GetClassId()));
+        Smi::Handle(zone_, Smi::New(receiver().GetClassId()));
     ASSERT(target_code.HasMonomorphicEntry());
     CodePatcher::PatchSwitchableCallAt(caller_frame_->pc(), caller_code_,
                                        expected_cid, target_code);
-    arguments_.SetArgAt(0, target_code);
-    arguments_.SetReturn(expected_cid);
+    ReturnAOT(target_code, expected_cid);
   } else {
-    ic_data.EnsureHasReceiverCheck(receiver_.GetClassId(), target_function);
+    ic_data.EnsureHasReceiverCheck(receiver().GetClassId(), target_function);
     if (number_of_checks > FLAG_max_polymorphic_checks) {
       // Switch to megamorphic call.
       const MegamorphicCache& cache = MegamorphicCache::Handle(
@@ -1935,20 +1832,60 @@
 
       CodePatcher::PatchSwitchableCallAt(caller_frame_->pc(), caller_code_,
                                          cache, stub);
-      arguments_.SetArgAt(0, stub);
-      arguments_.SetReturn(cache);
+      ReturnAOT(stub, cache);
     } else {
-      arguments_.SetArgAt(0, StubCode::ICCallThroughCode());
-      arguments_.SetReturn(ic_data);
+      ReturnAOT(StubCode::ICCallThroughCode(), ic_data);
     }
   }
 }
 #endif  // defined(DART_PRECOMPILED_RUNTIME)
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+void PatchableCallHandler::DoICDataMissJIT(const ICData& ic_data,
+                                           const Object& old_code,
+                                           const Function& target_function) {
+  ASSERT(ic_data.NumArgsTested() == caller_arguments_.length());
+
+  if (ic_data.NumArgsTested() == 1) {
+    ASSERT(old_code.raw() == StubCode::OneArgCheckInlineCache().raw() ||
+           old_code.raw() ==
+               StubCode::OneArgCheckInlineCacheWithExactnessCheck().raw() ||
+           old_code.raw() ==
+               StubCode::OneArgOptimizedCheckInlineCache().raw() ||
+           old_code.raw() ==
+               StubCode::OneArgOptimizedCheckInlineCacheWithExactnessCheck()
+                   .raw() ||
+           old_code.raw() == StubCode::ICCallBreakpoint().raw() ||
+           (old_code.IsNull() && !should_consider_patching()));
+    UpdateICDataWithTarget(ic_data, target_function);
+    if (should_consider_patching()) {
+      TrySwitchInstanceCall(thread_, caller_frame_, caller_code_,
+                            caller_function_, ic_data, target_function);
+    }
+    const Code& stub = Code::Handle(
+        zone_, ic_data.is_tracking_exactness()
+                   ? StubCode::OneArgCheckInlineCacheWithExactnessCheck().raw()
+                   : StubCode::OneArgCheckInlineCache().raw());
+    ReturnJIT(stub, ic_data, target_function);
+  } else {
+    ASSERT(old_code.raw() == StubCode::TwoArgsCheckInlineCache().raw() ||
+           old_code.raw() == StubCode::SmiAddInlineCache().raw() ||
+           old_code.raw() == StubCode::SmiLessInlineCache().raw() ||
+           old_code.raw() == StubCode::SmiEqualInlineCache().raw() ||
+           old_code.raw() ==
+               StubCode::TwoArgsOptimizedCheckInlineCache().raw() ||
+           old_code.raw() == StubCode::ICCallBreakpoint().raw() ||
+           (old_code.IsNull() && !should_consider_patching()));
+    UpdateICDataWithTarget(ic_data, target_function);
+    ReturnJIT(StubCode::TwoArgsCheckInlineCache(), ic_data, target_function);
+  }
+}
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+
 void PatchableCallHandler::DoMegamorphicMiss(const MegamorphicCache& data,
                                              const Function& target_function) {
   const String& name = String::Handle(zone_, data.target_name());
-  const Class& cls = Class::Handle(zone_, receiver_.clazz());
+  const Class& cls = Class::Handle(zone_, receiver().clazz());
   ASSERT(!cls.IsNull());
   const Array& descriptor =
       Array::CheckedHandle(zone_, data.arguments_descriptor());
@@ -1958,18 +1895,83 @@
                  cls.ToCString(), args_desc.TypeArgsLen(), name.ToCString());
   }
   if (target_function.IsNull()) {
-    arguments_.SetArgAt(0, StubCode::NoSuchMethodDispatcher());
-    arguments_.SetReturn(data);
+    ReturnJITorAOT(StubCode::NoSuchMethodDispatcher(), data, target_function);
     return;
   }
 
   // Insert function found into cache.
   const Smi& class_id = Smi::Handle(zone_, Smi::New(cls.id()));
   data.EnsureContains(class_id, target_function);
-  arguments_.SetArgAt(0, StubCode::MegamorphicCall());
+  ReturnJITorAOT(StubCode::MegamorphicCall(), data, target_function);
+}
+
+void PatchableCallHandler::UpdateICDataWithTarget(
+    const ICData& ic_data,
+    const Function& target_function) {
+  if (target_function.IsNull()) return;
+
+  // If, upon return of the runtime, we will invoke the target directly we have
+  // to increment the call count here in the ICData.
+  // If we instead only insert a new ICData entry and will return to the IC stub
+  // which will call the target, the stub will take care of the increment.
+  const bool call_target_directly =
+      miss_handler_ == MissHandler::kInlineCacheMiss;
+  const intptr_t invocation_count = call_target_directly ? 1 : 0;
+
+  if (caller_arguments_.length() == 1) {
+    auto exactness = StaticTypeExactnessState::NotTracking();
+#if !defined(DART_PRECOMPILED_RUNTIME)
+    if (ic_data.is_tracking_exactness()) {
+      exactness = receiver().IsNull()
+                      ? StaticTypeExactnessState::NotExact()
+                      : StaticTypeExactnessState::Compute(
+                            Type::Cast(AbstractType::Handle(
+                                ic_data.receivers_static_type())),
+                            receiver());
+    }
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+    ic_data.EnsureHasReceiverCheck(receiver().GetClassId(), target_function,
+                                   invocation_count, exactness);
+  } else {
+    GrowableArray<intptr_t> class_ids(caller_arguments_.length());
+    ASSERT(ic_data.NumArgsTested() == caller_arguments_.length());
+    for (intptr_t i = 0; i < caller_arguments_.length(); i++) {
+      class_ids.Add(caller_arguments_[i]->GetClassId());
+    }
+    ic_data.EnsureHasCheck(class_ids, target_function, invocation_count);
+  }
+}
+
+void PatchableCallHandler::ReturnAOT(const Code& stub, const Object& data) {
+  ASSERT(miss_handler_ == MissHandler::kSwitchableCallMiss);
+  arguments_.SetArgAt(0, stub);  // Second return value.
   arguments_.SetReturn(data);
 }
 
+void PatchableCallHandler::ReturnJIT(const Code& stub,
+                                     const Object& data,
+                                     const Function& target) {
+  // In JIT we can have two different miss handlers to which we return slightly
+  // differently.
+  if (miss_handler_ == MissHandler::kSwitchableCallMiss) {
+    arguments_.SetArgAt(0, stub);  // Second return value.
+    arguments_.SetReturn(data);
+  } else {
+    ASSERT(miss_handler_ == MissHandler::kInlineCacheMiss);
+    arguments_.SetReturn(target);
+  }
+}
+
+void PatchableCallHandler::ReturnJITorAOT(const Code& stub,
+                                          const Object& data,
+                                          const Function& target) {
+#if defined(DART_PRECOMPILED_MODE)
+  ReturnAOT(stub, data);
+#else
+  ReturnJIT(stub, data, target);
+#endif
+}
+
 ICDataPtr PatchableCallHandler::NewICData() {
   return ICData::New(caller_function_, name_, args_descriptor_, DeoptId::kNone,
                      /*num_args_tested=*/1, ICData::kInstance);
@@ -2045,8 +2047,43 @@
     default:
       UNREACHABLE();
   }
-  const Class& cls = Class::Handle(zone_, receiver_.clazz());
-  return Resolve(thread_, zone_, cls, name_, args_descriptor_);
+  const Class& cls = Class::Handle(zone_, receiver().clazz());
+  return Resolve(thread_, zone_, caller_arguments_, cls, name_,
+                 args_descriptor_);
+}
+
+void PatchableCallHandler::ResolveSwitchAndReturn(const Object& old_data) {
+  // Find out actual target (which can be time consuminmg) without holding any
+  // locks.
+  const auto& target_function =
+      Function::Handle(zone_, ResolveTargetFunction(old_data));
+
+  auto& code = Code::Handle(zone_);
+  auto& data = Object::Handle(zone_);
+
+  // We ensure any transition in a patchable calls are done in an atomic
+  // manner, we ensure we always transition forward (e.g. Monomorphic ->
+  // Polymorphic).
+  //
+  // Mutators are only stopped if we actually need to patch a patchable call.
+  // We may not do that if we e.g. just add one more check to an ICData.
+  SafepointMutexLocker ml(thread_->isolate_group()->patchable_call_mutex());
+
+#if defined(DART_PRECOMPILED_RUNTIME)
+  data =
+      CodePatcher::GetSwitchableCallDataAt(caller_frame_->pc(), caller_code_);
+  DEBUG_ONLY(code = CodePatcher::GetSwitchableCallTargetAt(caller_frame_->pc(),
+                                                           caller_code_));
+#else
+  if (should_consider_patching()) {
+    code ^= CodePatcher::GetInstanceCallAt(caller_frame_->pc(), caller_code_,
+                                           &data);
+  } else {
+    ASSERT(old_data.IsICData() || old_data.IsMegamorphicCache());
+    data = old_data.raw();
+  }
+#endif
+  HandleMiss(data, code, target_function);
 }
 
 void PatchableCallHandler::HandleMiss(const Object& old_data,
@@ -2079,13 +2116,12 @@
       DoMonomorphicMissJIT(old_data, target_function);
       break;
     case kICDataCid:
-      // Right now ICData misses wil go to the normal ICData miss handler, not
-      // to SwitchableCall miss handler.
-      UNREACHABLE();
+      DoICDataMissJIT(ICData::Cast(old_data), old_code, target_function);
       break;
 #endif  // defined(DART_PRECOMPILED_RUNTIME)
     case kMegamorphicCacheCid:
-      ASSERT(old_code.raw() == StubCode::MegamorphicCall().raw());
+      ASSERT(old_code.raw() == StubCode::MegamorphicCall().raw() ||
+             (old_code.IsNull() && !should_consider_patching()));
       DoMegamorphicMiss(MegamorphicCache::Cast(old_data), target_function);
       break;
     default:
@@ -2093,6 +2129,60 @@
   }
 }
 
+static void InlineCacheMissHandler(Thread* thread,
+                                   Zone* zone,
+                                   const GrowableArray<const Instance*>& args,
+                                   const ICData& ic_data,
+                                   NativeArguments native_arguments) {
+#if !defined(DART_PRECOMPILED_RUNTIME)
+  DartFrameIterator iterator(thread,
+                             StackFrameIterator::kNoCrossThreadIteration);
+  StackFrame* caller_frame = iterator.NextFrame();
+  const auto& caller_code = Code::Handle(zone, caller_frame->LookupDartCode());
+  const auto& caller_function =
+      Function::Handle(zone, caller_frame->LookupDartFunction());
+
+  PatchableCallHandler handler(thread, args, MissHandler::kInlineCacheMiss,
+                               native_arguments, caller_frame, caller_code,
+                               caller_function);
+
+  handler.ResolveSwitchAndReturn(ic_data);
+#else
+  UNREACHABLE();
+#endif  // !defined(DART_PRECOMPILED_RUNTIME)
+}
+
+// Handles inline cache misses by updating the IC data array of the call site.
+//   Arg0: Receiver object.
+//   Arg1: IC data object.
+//   Returns: target function with compiled code or null.
+// Modifies the instance call to hold the updated IC data array.
+DEFINE_RUNTIME_ENTRY(InlineCacheMissHandlerOneArg, 2) {
+  const Instance& receiver = Instance::CheckedHandle(zone, arguments.ArgAt(0));
+  const ICData& ic_data = ICData::CheckedHandle(zone, arguments.ArgAt(1));
+  RELEASE_ASSERT(!FLAG_precompiled_mode);
+  GrowableArray<const Instance*> args(1);
+  args.Add(&receiver);
+  InlineCacheMissHandler(thread, zone, args, ic_data, arguments);
+}
+
+// Handles inline cache misses by updating the IC data array of the call site.
+//   Arg0: Receiver object.
+//   Arg1: Argument after receiver.
+//   Arg2: IC data object.
+//   Returns: target function with compiled code or null.
+// Modifies the instance call to hold the updated IC data array.
+DEFINE_RUNTIME_ENTRY(InlineCacheMissHandlerTwoArgs, 3) {
+  const Instance& receiver = Instance::CheckedHandle(zone, arguments.ArgAt(0));
+  const Instance& other = Instance::CheckedHandle(zone, arguments.ArgAt(1));
+  const ICData& ic_data = ICData::CheckedHandle(zone, arguments.ArgAt(2));
+  RELEASE_ASSERT(!FLAG_precompiled_mode);
+  GrowableArray<const Instance*> args(2);
+  args.Add(&receiver);
+  args.Add(&other);
+  InlineCacheMissHandler(thread, zone, args, ic_data, arguments);
+}
+
 // Handle the first use of an instance call
 //   Arg1: Receiver.
 //   Arg0: Stub out.
@@ -2116,42 +2206,20 @@
   const Function& caller_function =
       Function::Handle(zone, caller_frame->LookupDartFunction());
 
-  PatchableCallHandler handler(thread, receiver, arguments, caller_frame,
-                               caller_code, caller_function);
-
-  // Find out actual target (which can be time consuminmg) without holding any
-  // locks.
-  Object& old_data = Object::Handle(zone);
+  auto& old_data = Object::Handle(zone);
 #if defined(DART_PRECOMPILED_RUNTIME)
   old_data =
       CodePatcher::GetSwitchableCallDataAt(caller_frame->pc(), caller_code);
 #else
   CodePatcher::GetInstanceCallAt(caller_frame->pc(), caller_code, &old_data);
 #endif
-  const Function& target_function =
-      Function::Handle(zone, handler.ResolveTargetFunction(old_data));
 
-  {
-    // We ensure any transition in a patchable calls are done in an atomic
-    // manner, we ensure we always transition forward (e.g. Monomorphic ->
-    // Polymorphic).
-    //
-    // Mutators are only stopped if we actually need to patch a patchable call.
-    // We may not do that if we e.g. just add one more check to an ICData.
-    SafepointMutexLocker ml(thread->isolate_group()->patchable_call_mutex());
-
-    auto& old_code = Code::Handle(zone);
-#if defined(DART_PRECOMPILED_RUNTIME)
-    old_data =
-        CodePatcher::GetSwitchableCallDataAt(caller_frame->pc(), caller_code);
-    DEBUG_ONLY(old_code = CodePatcher::GetSwitchableCallTargetAt(
-                   caller_frame->pc(), caller_code));
-#else
-    old_code ^= CodePatcher::GetInstanceCallAt(caller_frame->pc(), caller_code,
-                                               &old_data);
-#endif
-    handler.HandleMiss(old_data, old_code, target_function);
-  }
+  GrowableArray<const Instance*> caller_arguments(1);
+  caller_arguments.Add(&receiver);
+  PatchableCallHandler handler(thread, caller_arguments,
+                               MissHandler::kSwitchableCallMiss, arguments,
+                               caller_frame, caller_code, caller_function);
+  handler.ResolveSwitchAndReturn(old_data);
 }
 
 // Used to find the correct receiver and function to invoke or to fall back to
diff --git a/runtime/vm/source_report.cc b/runtime/vm/source_report.cc
index 84f5495..3f074ff 100644
--- a/runtime/vm/source_report.cc
+++ b/runtime/vm/source_report.cc
@@ -565,6 +565,19 @@
 
     // Visit all closures for this isolate.
     VisitClosures(&ranges);
+
+    // Output constant coverage if coverage is requested.
+    if (IsReportRequested(kCoverage)) {
+      // Find all scripts. We need to go though all scripts because a script
+      // (even one we don't want) can add coverage to another library (i.e.
+      // potentially one we want).
+      DirectChainedHashMap<ScriptTableTrait> local_script_table;
+      GrowableArray<ScriptTableEntry*> local_script_table_entries;
+      CollectAllScripts(&local_script_table, &local_script_table_entries);
+      CollectConstConstructorCoverageFromScripts(&local_script_table_entries,
+                                                 &ranges);
+      CleanupCollectedScripts(&local_script_table, &local_script_table_entries);
+    }
   }
 
   // Print the script table.
@@ -572,5 +585,102 @@
   PrintScriptTable(&scripts);
 }
 
+void SourceReport::CollectAllScripts(
+    DirectChainedHashMap<ScriptTableTrait>* local_script_table,
+    GrowableArray<ScriptTableEntry*>* local_script_table_entries) {
+  ScriptTableEntry wrapper;
+  const GrowableObjectArray& libs = GrowableObjectArray::Handle(
+      zone(), thread()->isolate()->object_store()->libraries());
+  Library& lib = Library::Handle(zone());
+  Script& scriptRef = Script::Handle(zone());
+  for (int i = 0; i < libs.Length(); i++) {
+    lib ^= libs.At(i);
+    const Array& scripts = Array::Handle(zone(), lib.LoadedScripts());
+    for (intptr_t j = 0; j < scripts.Length(); j++) {
+      scriptRef ^= scripts.At(j);
+      const String& url = String::Handle(zone(), scriptRef.url());
+      wrapper.key = &url;
+      wrapper.script = &Script::Handle(zone(), scriptRef.raw());
+      ScriptTableEntry* pair = local_script_table->LookupValue(&wrapper);
+      if (pair != NULL) {
+        // Existing one.
+        continue;
+      }
+      // New one. Insert.
+      ScriptTableEntry* tmp = new ScriptTableEntry();
+      tmp->key = &url;
+      tmp->index = next_script_index_++;
+      tmp->script = wrapper.script;
+      local_script_table_entries->Add(tmp);
+      local_script_table->Insert(tmp);
+    }
+  }
+}
+
+void SourceReport::CleanupCollectedScripts(
+    DirectChainedHashMap<ScriptTableTrait>* local_script_table,
+    GrowableArray<ScriptTableEntry*>* local_script_table_entries) {
+  for (intptr_t i = 0; i < local_script_table_entries->length(); i++) {
+    delete local_script_table_entries->operator[](i);
+    local_script_table_entries->operator[](i) = NULL;
+  }
+  local_script_table_entries->Clear();
+  local_script_table->Clear();
+}
+
+void SourceReport::CollectConstConstructorCoverageFromScripts(
+    GrowableArray<ScriptTableEntry*>* local_script_table_entries,
+    JSONArray* ranges) {
+  // Now output the wanted constant coverage.
+  for (intptr_t i = 0; i < local_script_table_entries->length(); i++) {
+    const Script* script = local_script_table_entries->At(i)->script;
+    bool script_ok = true;
+    if (script_ != NULL && !script_->IsNull()) {
+      if (script->raw() != script_->raw()) {
+        // This is the wrong script.
+        script_ok = false;
+      }
+    }
+
+    // Whether we want *this* script or not we need to look at the constant
+    // constructor coverage. Any of those could be in a script we *do* want.
+    {
+      Script& scriptRef = Script::Handle(zone());
+      const Array& constructors =
+          Array::Handle(kernel::CollectConstConstructorCoverageFrom(*script));
+      intptr_t constructors_count = constructors.Length();
+      Function& constructor = Function::Handle(zone());
+      Code& code = Code::Handle(zone());
+      for (intptr_t i = 0; i < constructors_count; i++) {
+        constructor ^= constructors.At(i);
+        // Check if we want coverage for this constructor.
+        if (ShouldSkipFunction(constructor)) {
+          continue;
+        }
+        scriptRef ^= constructor.script();
+        code ^= constructor.unoptimized_code();
+        const TokenPosition begin_pos = constructor.token_pos();
+        const TokenPosition end_pos = constructor.end_token_pos();
+        JSONObject range(ranges);
+        range.AddProperty("scriptIndex", GetScriptIndex(scriptRef));
+        range.AddProperty("compiled",
+                          !code.IsNull());  // Does this make a difference?
+        range.AddProperty("startPos", begin_pos);
+        range.AddProperty("endPos", end_pos);
+
+        JSONObject cov(&range, "coverage");
+        {
+          JSONArray hits(&cov, "hits");
+          hits.AddValue(begin_pos);
+        }
+        {
+          JSONArray misses(&cov, "misses");
+          // No misses
+        }
+      }
+    }
+  }
+}
+
 }  // namespace dart
 #endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/source_report.h b/runtime/vm/source_report.h
index 0a3ddb1..4ec20c9 100644
--- a/runtime/vm/source_report.h
+++ b/runtime/vm/source_report.h
@@ -112,6 +112,18 @@
     }
   };
 
+  void CollectAllScripts(
+      DirectChainedHashMap<ScriptTableTrait>* local_script_table,
+      GrowableArray<ScriptTableEntry*>* local_script_table_entries);
+
+  void CleanupCollectedScripts(
+      DirectChainedHashMap<ScriptTableTrait>* local_script_table,
+      GrowableArray<ScriptTableEntry*>* local_script_table_entries);
+
+  void CollectConstConstructorCoverageFromScripts(
+      GrowableArray<ScriptTableEntry*>* local_script_table_entries,
+      JSONArray* ranges);
+
   intptr_t report_set_;
   CompileMode compile_mode_;
   Thread* thread_;
diff --git a/tools/VERSION b/tools/VERSION
index aa43b14..679ae2d 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 12
 PATCH 0
-PRERELEASE 131
+PRERELEASE 132
 PRERELEASE_PATCH 0
\ No newline at end of file