Version 2.16.0-17.0.dev

Merge commit 'c6fe9e21f005537ecfff947a0e5dbab129a17c26' into 'dev'
diff --git a/pkg/front_end/test/comments_on_certain_arguments_tool.dart b/pkg/front_end/test/comments_on_certain_arguments_tool.dart
index ce02610..49d1ecbe 100644
--- a/pkg/front_end/test/comments_on_certain_arguments_tool.dart
+++ b/pkg/front_end/test/comments_on_certain_arguments_tool.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:convert' show utf8;
 
 import 'dart:io'
@@ -56,7 +54,7 @@
 
 Set<Uri> libUris = {};
 
-Component component;
+late Component component;
 
 Future<void> main(List<String> args) async {
   api.CompilerOptions compilerOptions = getOptions();
@@ -100,8 +98,9 @@
     List<Uri> editsPerformed = [];
     for (Uri uri in edits.keys) {
       print("\n\n\n");
-      if (edits[uri] != null && edits[uri].isNotEmpty) {
-        String update;
+      List<Edit>? theseEdits = edits[uri];
+      if (theseEdits != null && theseEdits.isNotEmpty) {
+        String? update;
         while (update != "y" &&
             update != "yes" &&
             update != "n" &&
@@ -111,9 +110,8 @@
         }
         if (update != "y" && update != "yes") continue;
 
-        List<Edit> theseEdits = edits[uri];
         theseEdits.sort();
-        String content = utf8.decode(component.uriToSource[uri].source,
+        String content = utf8.decode(component.uriToSource[uri]!.source,
             allowMalformed: true);
         StringBuffer sb = new StringBuffer();
         int latest = 0;
@@ -128,7 +126,7 @@
             case EditType.Delete:
               print(edit);
               // We "delete" by skipping...
-              latest = edit.offset + edit.length;
+              latest = edit.offset + edit.length!;
               break;
           }
         }
@@ -195,19 +193,19 @@
   @override
   void visitSuperMethodInvocation(SuperMethodInvocation node) {
     super.visitSuperMethodInvocation(node);
-    note(node.interfaceTargetReference.node, node.arguments, node);
+    note(node.interfaceTargetReference!.node!, node.arguments, node);
   }
 
   @override
   void visitStaticInvocation(StaticInvocation node) {
     super.visitStaticInvocation(node);
-    note(node.targetReference.node, node.arguments, node);
+    note(node.targetReference.node!, node.arguments, node);
   }
 
   @override
   void visitConstructorInvocation(ConstructorInvocation node) {
     super.visitConstructorInvocation(node);
-    note(node.targetReference.node, node.arguments, node);
+    note(node.targetReference.node!, node.arguments, node);
   }
 
   void note(
@@ -223,28 +221,25 @@
 
     for (int i = 0; i < arguments.positional.length; i++) {
       bool wantComment = false;
-      if (arguments.positional[i] is NullLiteral ||
-          arguments.positional[i] is BoolLiteral ||
-          arguments.positional[i] is IntLiteral) {
+      Expression argument = arguments.positional[i];
+      if (argument is NullLiteral ||
+          argument is BoolLiteral ||
+          argument is IntLiteral) {
         wantComment = true;
-      } else if (arguments.positional[i] is MapLiteral) {
-        MapLiteral literal = arguments.positional[i];
-        if (literal.entries.isEmpty) wantComment = true;
-      } else if (arguments.positional[i] is ListLiteral) {
-        ListLiteral literal = arguments.positional[i];
-        if (literal.expressions.isEmpty) wantComment = true;
-      } else if (arguments.positional[i] is InstanceInvocation) {
-        InstanceInvocation methodInvocation = arguments.positional[i];
-        if (methodInvocation.receiver is NullLiteral ||
-            methodInvocation.receiver is IntLiteral ||
-            methodInvocation.receiver is BoolLiteral) {
+      } else if (argument is MapLiteral) {
+        if (argument.entries.isEmpty) wantComment = true;
+      } else if (argument is ListLiteral) {
+        if (argument.expressions.isEmpty) wantComment = true;
+      } else if (argument is InstanceInvocation) {
+        if (argument.receiver is NullLiteral ||
+            argument.receiver is IntLiteral ||
+            argument.receiver is BoolLiteral) {
           wantComment = true;
         }
-      } else if (arguments.positional[i] is DynamicInvocation) {
-        DynamicInvocation methodInvocation = arguments.positional[i];
-        if (methodInvocation.receiver is NullLiteral ||
-            methodInvocation.receiver is IntLiteral ||
-            methodInvocation.receiver is BoolLiteral) {
+      } else if (argument is DynamicInvocation) {
+        if (argument.receiver is NullLiteral ||
+            argument.receiver is IntLiteral ||
+            argument.receiver is BoolLiteral) {
           wantComment = true;
         }
       }
@@ -269,10 +264,10 @@
     return;
   }
   if (argumentExpression.fileOffset == -1) return;
-  Location location = argumentExpression.location;
-  Token token = cache[location.file];
+  Location location = argumentExpression.location!;
+  Token token = cache[location.file]!;
   while (token.offset != argumentExpression.fileOffset) {
-    token = token.next;
+    token = token.next!;
     if (token.isEof) {
       throw "Couldn't find token for $argumentExpression "
           "(${argumentExpression.fileOffset}).";
@@ -280,7 +275,7 @@
   }
   bool foundComment = false;
   List<CommentToken> badComments = [];
-  CommentToken commentToken = token.precedingComments;
+  CommentToken? commentToken = token.precedingComments;
   while (commentToken != null) {
     if (commentToken.lexeme == expectedComment) {
       // Exact match.
@@ -291,17 +286,16 @@
         commentToken.lexeme.endsWith("= */")) {
       badComments.add(commentToken);
     }
-    commentToken = commentToken.next;
+    commentToken = commentToken.next as CommentToken?;
   }
   if (badComments.isNotEmpty) {
     for (CommentToken comment in badComments) {
       Location calculatedLocation =
-          component.getLocation(location.file, comment.offset);
+          component.getLocation(location.file, comment.offset)!;
       print("Please remove comment of length ${comment.lexeme.length} at "
           "${comment.offset} => "
           "${calculatedLocation}");
-      edits[location.file] ??= [];
-      edits[location.file]
+      (edits[location.file] ??= [])
           .add(new Edit.delete(comment.offset, comment.lexeme.length));
     }
   }
@@ -309,12 +303,12 @@
     return;
   }
   Location calculatedLocation =
-      component.getLocation(location.file, token.offset);
+      component.getLocation(location.file, token.offset)!;
   print("Please add comment $expectedComment at "
       "${token.offset} => "
       "${calculatedLocation}");
-  edits[location.file] ??= [];
-  edits[location.file].add(new Edit.insert(token.offset, expectedComment));
+  (edits[location.file] ??= [])
+      .add(new Edit.insert(token.offset, expectedComment));
 }
 
 Map<Uri, List<Edit>> edits = {};
@@ -323,8 +317,8 @@
 
 class Edit implements Comparable<Edit> {
   final int offset;
-  final int length;
-  final String insertData;
+  final int? length;
+  final String? insertData;
   final EditType editType;
   Edit.insert(this.offset, this.insertData)
       : editType = EditType.Insert,
diff --git a/pkg/front_end/test/compile_benchmark.dart b/pkg/front_end/test/compile_benchmark.dart
index 2ad309a..c244da3 100644
--- a/pkg/front_end/test/compile_benchmark.dart
+++ b/pkg/front_end/test/compile_benchmark.dart
@@ -1,5 +1,3 @@
-// @dart = 2.9
-
 import 'dart:convert';
 import 'dart:io';
 import 'dart:typed_data';
@@ -23,7 +21,7 @@
     Platform.script.resolve("compile_benchmark_helper.dart");
 
 void main(List<String> args) {
-  List<String> arguments;
+  List<String>? arguments;
   bool tryToAnnotate = false;
   bool tryToSlowDown = false;
   bool timeInsteadOfCount = false;
@@ -135,7 +133,7 @@
   if (tryToSlowDown) {
     didSomething = true;
     for (Procedure p in sortedProcedures) {
-      Uri busyWaiting = busyWaitProcedure(
+      Uri? busyWaiting = busyWaitProcedure(
           dillData,
           tmp.uri,
           (lib) => lib.importUri == p.enclosingLibrary.importUri,
@@ -245,13 +243,14 @@
 ///
 /// The annotation is copied from the [preferInlineMe] method in the helper.
 Uri preferInlineProcedure(List<int> dillData, Uri tmp,
-    bool libraryMatcher(Library lib), String className, String procedureName) {
+    bool libraryMatcher(Library lib), String? className, String procedureName) {
   Component component = new Component();
   new BinaryBuilder(dillData, disableLazyReading: true)
       .readComponent(component);
   Procedure preferInlineMeProcedure = getProcedure(component,
       (lib) => lib.fileUri == benchmarkHelper, null, "preferInlineMe");
-  ConstantExpression annotation = preferInlineMeProcedure.annotations.single;
+  ConstantExpression annotation =
+      preferInlineMeProcedure.annotations.single as ConstantExpression;
   Procedure markProcedure =
       getProcedure(component, libraryMatcher, className, procedureName);
   markProcedure.addAnnotation(
@@ -268,8 +267,8 @@
 ///
 /// This will make the procedure busy-wait approximately 0.002 ms for each
 /// invocation (+ whatever overhead and imprecision).
-Uri busyWaitProcedure(List<int> dillData, Uri tmp,
-    bool libraryMatcher(Library lib), String className, String procedureName) {
+Uri? busyWaitProcedure(List<int> dillData, Uri tmp,
+    bool libraryMatcher(Library lib), String? className, String procedureName) {
   Component component = new Component();
   new BinaryBuilder(dillData, disableLazyReading: true)
       .readComponent(component);
@@ -280,7 +279,7 @@
       getProcedure(component, libraryMatcher, className, procedureName);
   if (markProcedure.function.body == null) return null;
 
-  Statement orgBody = markProcedure.function.body;
+  Statement orgBody = markProcedure.function.body as Statement;
   markProcedure.function.body = new Block([
     new ExpressionStatement(new StaticInvocation(
         busyWaitProcedure, new Arguments([new IntLiteral(2 /* 0.002 ms */)]))),
@@ -375,13 +374,13 @@
     if (node.function.body == null) return;
     int procedureNum = procedures.length;
     procedures.add(node);
-    Statement orgBody = node.function.body;
+    Statement orgBody = node.function.body as Statement;
     node.function.body = new Block([
       new ExpressionStatement(new StaticInvocation(registerCallProcedure,
           new Arguments([new IntLiteral(procedureNum)]))),
       orgBody
     ]);
-    node.function.body.parent = node.function;
+    node.function.body!.parent = node.function;
   }
 }
 
@@ -408,7 +407,7 @@
     if (node.function.dartAsyncMarker != AsyncMarker.Sync) return;
     int procedureNum = procedures.length;
     procedures.add(node);
-    Statement orgBody = node.function.body;
+    Statement orgBody = node.function.body as Statement;
     // Rewrite as
     // {
     //    registerCallStartProcedure(x);
@@ -428,12 +427,12 @@
       )
     ]);
     node.function.body = block;
-    node.function.body.parent = node.function;
+    node.function.body!.parent = node.function;
   }
 }
 
 Procedure getProcedure(Component component, bool libraryMatcher(Library lib),
-    String className, String procedureName) {
+    String? className, String procedureName) {
   Library lib = component.libraries.where(libraryMatcher).single;
   List<Procedure> procedures = lib.procedures;
   if (className != null) {
@@ -444,7 +443,7 @@
   return procedures.where((p) => p.name.text == procedureName).single;
 }
 
-List<int> runXTimes(int x, List<String> arguments, [List<dynamic> stdout]) {
+List<int> runXTimes(int x, List<String> arguments, [List<dynamic>? stdout]) {
   List<int> result = [];
   Stopwatch stopwatch = new Stopwatch()..start();
   for (int i = 0; i < x; i++) {
diff --git a/pkg/front_end/test/crashing_test_case_minimizer.dart b/pkg/front_end/test/crashing_test_case_minimizer.dart
index c5e60d5..55c2b69 100644
--- a/pkg/front_end/test/crashing_test_case_minimizer.dart
+++ b/pkg/front_end/test/crashing_test_case_minimizer.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:convert' show jsonDecode;
 
 import 'dart:io' show File;
@@ -20,8 +18,8 @@
 // parser on it and verifies that no syntax errors have been introduced.
 
 Future<void> main(List<String> arguments) async {
-  String filename;
-  Uri loadJson;
+  String? filename;
+  Uri? loadJson;
   for (String arg in arguments) {
     if (arg.startsWith("--json=")) {
       String json = arg.substring("--json=".length);
@@ -92,7 +90,7 @@
     if (settings.noPlatform) {
       int i = 0;
       while (settings.platformUri == null ||
-          new File.fromUri(settings.platformUri).existsSync()) {
+          new File.fromUri(settings.platformUri!).existsSync()) {
         settings.platformUri = Uri.base.resolve("nonexisting_$i");
         i++;
       }
@@ -100,7 +98,7 @@
       if (settings.platformUri == null) {
         throw "No platform given. Use --platform=/path/to/platform.dill";
       }
-      if (!new File.fromUri(settings.platformUri).existsSync()) {
+      if (!new File.fromUri(settings.platformUri!).existsSync()) {
         throw "The platform file '${settings.platformUri}' doesn't exist";
       }
     }
diff --git a/pkg/front_end/test/dartdoc_test_test.dart b/pkg/front_end/test/dartdoc_test_test.dart
index 455a8cf..35de337 100644
--- a/pkg/front_end/test/dartdoc_test_test.dart
+++ b/pkg/front_end/test/dartdoc_test_test.dart
@@ -1,5 +1,3 @@
-// @dart = 2.9
-
 import 'dart:convert';
 import 'dart:typed_data';
 
@@ -393,7 +391,7 @@
 }
 
 int expectCalls = 0;
-String expectCategory;
+String? expectCategory;
 
 void expect(dynamic actual, dynamic expected) {
   expectCalls++;
@@ -461,23 +459,23 @@
   return false;
 }
 
-impl.CommentString extractFirstComment(String test) {
+impl.CommentString? extractFirstComment(String test) {
   Token firstToken = impl.scanRawBytes(utf8.encode(test) as Uint8List);
   Token token = firstToken;
   while (true) {
-    CommentToken comment = token.precedingComments;
+    CommentToken? comment = token.precedingComments;
     if (comment != null) {
       return impl.extractComments(comment, test);
     }
     if (token.isEof) break;
-    Token next = token.next;
+    Token? next = token.next;
     if (next == null) break;
     token = next;
   }
   return null;
 }
 
-List<impl.Test> extractTests(String test, [Uri uri]) {
+List<impl.Test> extractTests(String test, [Uri? uri]) {
   return impl.extractTests(utf8.encode(test) as Uint8List,
       uri ?? new Uri(scheme: "darttest", path: "/foo.dart"));
 }
diff --git a/pkg/front_end/test/dartdoctest_suite.dart b/pkg/front_end/test/dartdoctest_suite.dart
index 447ab20..d71cd57 100644
--- a/pkg/front_end/test/dartdoctest_suite.dart
+++ b/pkg/front_end/test/dartdoctest_suite.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:testing/testing.dart'
     show Chain, ChainContext, Result, Step, TestDescription, runMe;
 
diff --git a/pkg/front_end/test/deps_git_test.dart b/pkg/front_end/test/deps_git_test.dart
index c542bc5..2017352 100644
--- a/pkg/front_end/test/deps_git_test.dart
+++ b/pkg/front_end/test/deps_git_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE.md file.
 
-// @dart = 2.9
-
 import 'dart:io';
 
 import 'package:_fe_analyzer_shared/src/messages/severity.dart';
@@ -75,7 +73,7 @@
         new DillTarget(ticker, uriTranslator, c.options.target);
     KernelTarget kernelTarget =
         new KernelTarget(c.fileSystem, false, dillTarget, uriTranslator);
-    Uri platform = c.options.sdkSummary;
+    Uri? platform = c.options.sdkSummary;
     if (platform != null) {
       var bytes = new File.fromUri(platform).readAsBytesSync();
       var platformComponent = loadComponentFromBytes(bytes);
@@ -84,7 +82,7 @@
     }
 
     kernelTarget.setEntryPoints(c.options.inputs);
-    await dillTarget.buildOutlines();
+    dillTarget.buildOutlines();
     await kernelTarget.loader.buildOutlines();
     return new List<Uri>.from(c.dependencies);
   });
diff --git a/pkg/front_end/test/desugar_test.dart b/pkg/front_end/test/desugar_test.dart
index 82fdd4a..18342bb 100644
--- a/pkg/front_end/test/desugar_test.dart
+++ b/pkg/front_end/test/desugar_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 /// Test to ensure that desugaring APIs used by clients like dart2js are
 /// always up to date.
 ///
@@ -33,13 +31,13 @@
 
 Future<void> testRedirectingFactoryDirect() async {
   var component = await compileUnit(['a.dart'], {'a.dart': aSource});
-  checkIsRedirectingFactory(component, 'a.dart', 'A', 'foo');
+  checkIsRedirectingFactory(component!, 'a.dart', 'A', 'foo');
   checkIsRedirectingFactory(component, 'core', 'Uri', 'file');
 }
 
 Future<void> testRedirectingFactorySerialized() async {
   var component = await compileUnit(['a.dart'], {'a.dart': aSource});
-  var bytes = serializeComponent(component);
+  var bytes = serializeComponent(component!);
   component = new ir.Component();
   new BinaryBuilder(bytes).readComponent(component);
   checkIsRedirectingFactory(component, 'a.dart', 'A', 'foo');
@@ -62,8 +60,8 @@
   var lib =
       component.libraries.firstWhere((l) => l.importUri.path.endsWith(uriPath));
   var cls = lib.classes.firstWhere((c) => c.name == className);
-  ir.Procedure member =
-      cls.members.firstWhere((m) => m.name.text == constructorName);
+  ir.Procedure member = cls.members
+      .firstWhere((m) => m.name.text == constructorName) as ir.Procedure;
   Expect.isTrue(
       member.kind == ir.ProcedureKind.Factory, "$member is not a factory");
   Expect.isTrue(api.isRedirectingFactory(member));
diff --git a/pkg/front_end/test/dijkstras_sssp_algorithm.dart b/pkg/front_end/test/dijkstras_sssp_algorithm.dart
index 1c0a623..398ea2c 100644
--- a/pkg/front_end/test/dijkstras_sssp_algorithm.dart
+++ b/pkg/front_end/test/dijkstras_sssp_algorithm.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:collection';
 
 /// Dijkstra's algorithm for single source shortest path.
@@ -21,8 +19,8 @@
   DijkstrasAlgorithm(Iterable<GraphNode<E>> graphNodes, GraphNode<E> source,
       int Function(E, E) comparator, int Function(E, E) distance) {
     SplayTreeSet<GraphNode<E>> q = new SplayTreeSet<GraphNode<E>>((a, b) {
-      int distA = dist[a];
-      int distB = dist[b];
+      int? distA = dist[a];
+      int? distB = dist[b];
 
       int when0() {
         if (identical(a, b)) return 0;
@@ -39,7 +37,7 @@
       if (distA == null && distB == null) {
         return when0();
       }
-      if (distA < distB) return -1;
+      if (distA! < distB!) return -1;
       if (distA > distB) return 1;
       return when0();
     });
@@ -56,7 +54,7 @@
 
     while (q.isNotEmpty) {
       GraphNode<E> u = q.first;
-      int distToU = dist[u];
+      int? distToU = dist[u];
       if (distToU == null) {
         // No path to any of the remaining ${q.length} nodes.
         break;
@@ -68,7 +66,7 @@
         int distanceUToV = distance(u.node, v.node);
         if (distanceUToV < 0) throw "Got negative distance. That's not allowed";
         int alt = distToU + distanceUToV;
-        int distToV = dist[v];
+        int? distToV = dist[v];
         if (distToV == null || alt < distToV) {
           // Decrease length (decrease priority in priority queue).
           q.remove(v);
@@ -85,7 +83,7 @@
     GraphNode<E> u = target;
     while (u == source || prev[u] != null) {
       path.add(u.node);
-      u = prev[u];
+      u = prev[u]!;
     }
     return path.reversed.toList();
   }
diff --git a/pkg/front_end/test/dill_round_trip_test.dart b/pkg/front_end/test/dill_round_trip_test.dart
index 32d5759..716b186 100644
--- a/pkg/front_end/test/dill_round_trip_test.dart
+++ b/pkg/front_end/test/dill_round_trip_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:front_end/src/fasta/kernel/utils.dart' show serializeComponent;
 
 import 'package:kernel/ast.dart' show Component;
diff --git a/pkg/front_end/test/explicit_creation_git_test.dart b/pkg/front_end/test/explicit_creation_git_test.dart
index 5028925..dc8e3a1 100644
--- a/pkg/front_end/test/explicit_creation_git_test.dart
+++ b/pkg/front_end/test/explicit_creation_git_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io';
 
 import 'package:_fe_analyzer_shared/src/messages/severity.dart';
@@ -86,15 +84,14 @@
 
   Stopwatch stopwatch = new Stopwatch()..start();
 
-  await CompilerContext.runWithOptions<List<Uri>>(options,
-      (CompilerContext c) async {
+  await CompilerContext.runWithOptions(options, (CompilerContext c) async {
     UriTranslator uriTranslator = await c.options.getUriTranslator();
     DillTarget dillTarget =
         new DillTarget(ticker, uriTranslator, c.options.target);
     KernelTarget kernelTarget =
         new KernelTargetTest(c.fileSystem, false, dillTarget, uriTranslator);
 
-    Uri platform = c.options.sdkSummary;
+    Uri? platform = c.options.sdkSummary;
     if (platform != null) {
       var bytes = new File.fromUri(platform).readAsBytesSync();
       var platformComponent = loadComponentFromBytes(bytes);
@@ -103,10 +100,9 @@
     }
 
     kernelTarget.setEntryPoints(c.options.inputs);
-    await dillTarget.buildOutlines();
+    dillTarget.buildOutlines();
     await kernelTarget.buildOutlines();
     await kernelTarget.buildComponent();
-    return null;
   });
 
   print("Done in ${stopwatch.elapsedMilliseconds} ms. "
@@ -138,7 +134,7 @@
   @override
   BodyBuilder createBodyBuilderForOutlineExpression(
       SourceLibraryBuilder library,
-      DeclarationBuilder declarationBuilder,
+      DeclarationBuilder? declarationBuilder,
       ModifierBuilder member,
       Scope scope,
       Uri fileUri) {
@@ -162,10 +158,10 @@
   BodyBuilder createListenerInternal(
       ModifierBuilder builder,
       Scope memberScope,
-      Scope formalParameterScope,
+      Scope? formalParameterScope,
       bool isDeclarationInstanceMember,
-      VariableDeclaration extensionThis,
-      List<TypeParameter> extensionTypeParameters,
+      VariableDeclaration? extensionThis,
+      List<TypeParameter>? extensionTypeParameters,
       TypeInferrer typeInferrer,
       ConstantContext constantContext) {
     return new BodyBuilderTest(
@@ -221,7 +217,7 @@
   @override
   BodyBuilderTest.forOutlineExpression(
       SourceLibraryBuilder library,
-      DeclarationBuilder declarationBuilder,
+      DeclarationBuilder? declarationBuilder,
       ModifierBuilder member,
       Scope scope,
       Uri fileUri)
@@ -230,18 +226,18 @@
 
   @override
   Expression buildConstructorInvocation(
-      TypeDeclarationBuilder type,
+      TypeDeclarationBuilder? type,
       Token nameToken,
       Token nameLastToken,
-      Arguments arguments,
+      Arguments? arguments,
       String name,
-      List<TypeBuilder> typeArguments,
+      List<TypeBuilder>? typeArguments,
       int charOffset,
       Constness constness,
       {bool isTypeArgumentsInForest = false,
-      TypeDeclarationBuilder typeAliasBuilder,
-      UnresolvedKind unresolvedKind}) {
-    Token maybeNewOrConst = nameToken.previous;
+      TypeDeclarationBuilder? typeAliasBuilder,
+      required UnresolvedKind unresolvedKind}) {
+    Token maybeNewOrConst = nameToken.previous!;
     bool doReport = true;
     if (maybeNewOrConst is KeywordToken) {
       if (maybeNewOrConst.lexeme == "new" ||
diff --git a/pkg/front_end/test/ffi_test.dart b/pkg/front_end/test/ffi_test.dart
index 02cc7fbf..79f7ba7 100644
--- a/pkg/front_end/test/ffi_test.dart
+++ b/pkg/front_end/test/ffi_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:expect/expect.dart';
 
 void main() {
diff --git a/pkg/front_end/test/flutter_gallery_leak_tester.dart b/pkg/front_end/test/flutter_gallery_leak_tester.dart
index 973961d..84fe990 100644
--- a/pkg/front_end/test/flutter_gallery_leak_tester.dart
+++ b/pkg/front_end/test/flutter_gallery_leak_tester.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
@@ -11,7 +9,7 @@
 
 import "vm_service_heap_helper.dart" as helper;
 
-Completer completer;
+late Completer completer;
 
 Set<String> files = {};
 
@@ -36,7 +34,7 @@
 
   bool quicker = false;
   bool alternativeInvalidation = false;
-  String rootPath;
+  String? rootPath;
 
   for (String arg in args) {
     if (arg == "--quicker") {
diff --git a/pkg/front_end/test/generated_files_up_to_date_git_test.dart b/pkg/front_end/test/generated_files_up_to_date_git_test.dart
index 5161eef..78cdefb 100644
--- a/pkg/front_end/test/generated_files_up_to_date_git_test.dart
+++ b/pkg/front_end/test/generated_files_up_to_date_git_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE.md file.
 
-// @dart = 2.9
-
 import "dart:io" show File, exitCode;
 
 import "../tool/_fasta/generate_messages.dart" as generateMessages;
diff --git a/pkg/front_end/test/hot_reload_e2e_test.dart b/pkg/front_end/test/hot_reload_e2e_test.dart
index 9a2a0b9..105abbc 100644
--- a/pkg/front_end/test/hot_reload_e2e_test.dart
+++ b/pkg/front_end/test/hot_reload_e2e_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 /// Integration test that runs the incremental compiler, runs the compiled
 /// program, incrementally rebuild portions of the app, and triggers a hot
 /// reload on the running program.
@@ -45,12 +43,12 @@
 import 'tool/reload.dart' show RemoteVm;
 
 abstract class TestCase {
-  IncrementalKernelGenerator compiler;
-  MemoryFileSystem fs;
-  Directory outDir;
-  Uri outputUri;
-  List<Future<String>> lines;
-  Future programIsDone;
+  late IncrementalKernelGenerator compiler;
+  late MemoryFileSystem fs;
+  late Directory outDir;
+  late Uri outputUri;
+  List<Future<String>> lines = const [];
+  late Future programIsDone;
 
   String get name;
 
@@ -83,14 +81,14 @@
 
   Future<void> tearDown() async {
     outDir.deleteSync(recursive: true);
-    lines = null;
+    lines = const [];
   }
 
   Future<int> computeVmPort() async {
     var portLine = await lines[0];
     Expect.isTrue(observatoryPortRegExp.hasMatch(portLine));
     var match = observatoryPortRegExp.firstMatch(portLine);
-    return int.parse(match.group(1));
+    return int.parse(match!.group(1)!);
   }
 
   /// Request vm to resume execution
@@ -319,7 +317,7 @@
   compiler.invalidate(Uri.parse("org-dartlang-test:///b.dart"));
   compiler.invalidate(Uri.parse("org-dartlang-test:///c.dart"));
   var component = await compiler.computeDelta();
-  if (component != null && !component.libraries.isEmpty) {
+  if (!component.libraries.isEmpty) {
     await writeProgram(component, outputUri);
     return true;
   }
diff --git a/pkg/front_end/test/incremental_bulk_compiler_full.dart b/pkg/front_end/test/incremental_bulk_compiler_full.dart
index 61b3899..ce9a726 100644
--- a/pkg/front_end/test/incremental_bulk_compiler_full.dart
+++ b/pkg/front_end/test/incremental_bulk_compiler_full.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:expect/expect.dart' show Expect;
 
 import 'package:front_end/src/api_prototype/compiler_options.dart'
@@ -15,9 +13,6 @@
 import 'package:front_end/src/compute_platform_binaries_location.dart'
     show computePlatformBinariesLocation;
 
-import 'package:front_end/src/fasta/incremental_compiler.dart'
-    show IncrementalCompiler;
-
 import 'package:kernel/kernel.dart' show Component;
 
 import 'package:kernel/text/ast_to_text.dart'
@@ -49,7 +44,7 @@
     return result;
   }
 
-  IncrementalCompiler compiler;
+  IncrementalKernelGenerator? compiler;
 }
 
 CompilerOptions getOptions() {
@@ -78,9 +73,9 @@
 
     // "One shot compile"
     bool oneShotFailed = false;
-    List<int> oneShotSerialized;
+    late List<int> oneShotSerialized;
     try {
-      IncrementalCompiler compiler =
+      IncrementalKernelGenerator compiler =
           new IncrementalKernelGenerator(getOptions(), uri);
       oneShotSerialized = util.postProcess(await compiler.computeDelta());
     } catch (e) {
@@ -89,13 +84,13 @@
 
     // Bulk
     bool bulkFailed = false;
-    List<int> bulkSerialized;
+    late List<int> bulkSerialized;
     try {
       globalDebuggingNames = new NameSystem();
       if (context.compiler == null) {
         context.compiler = new IncrementalKernelGenerator(getOptions(), uri);
       }
-      Component bulkCompiledComponent = await context.compiler
+      Component bulkCompiledComponent = await context.compiler!
           .computeDelta(entryPoints: [uri], fullComponent: true);
       bulkSerialized = util.postProcess(bulkCompiledComponent);
     } catch (e) {
@@ -105,13 +100,13 @@
 
     // Compile again - the serialized output should be the same.
     bool bulk2Failed = false;
-    List<int> bulkSerialized2;
+    late List<int> bulkSerialized2;
     try {
       globalDebuggingNames = new NameSystem();
       if (context.compiler == null) {
         context.compiler = new IncrementalKernelGenerator(getOptions(), uri);
       }
-      Component bulkCompiledComponent = await context.compiler
+      Component bulkCompiledComponent = await context.compiler!
           .computeDelta(entryPoints: [uri], fullComponent: true);
       bulkSerialized2 = util.postProcess(bulkCompiledComponent);
     } catch (e) {
diff --git a/pkg/front_end/test/incremental_bulk_compiler_smoke_suite.dart b/pkg/front_end/test/incremental_bulk_compiler_smoke_suite.dart
index 03993a7..b60f41c 100644
--- a/pkg/front_end/test/incremental_bulk_compiler_smoke_suite.dart
+++ b/pkg/front_end/test/incremental_bulk_compiler_smoke_suite.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:testing/testing.dart' show Chain, runMe;
 
 import 'incremental_bulk_compiler_full.dart' show Context;
diff --git a/pkg/front_end/test/incremental_compiler_leak_tester.dart b/pkg/front_end/test/incremental_compiler_leak_tester.dart
index a9f3b81..d05460b 100644
--- a/pkg/front_end/test/incremental_compiler_leak_tester.dart
+++ b/pkg/front_end/test/incremental_compiler_leak_tester.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE.md file.
 
-// @dart = 2.9
-
 import 'dart:async';
 import 'dart:io';
 
@@ -29,12 +27,12 @@
   @override
   Future<void> run() async {
     vmService.VM vm = await serviceClient.getVM();
-    if (vm.isolates.length != 1) {
-      throw "Expected 1 isolate, got ${vm.isolates.length}";
+    if (vm.isolates!.length != 1) {
+      throw "Expected 1 isolate, got ${vm.isolates!.length}";
     }
-    vmService.IsolateRef isolateRef = vm.isolates.single;
-    await waitUntilIsolateIsRunnable(isolateRef.id);
-    await serviceClient.resume(isolateRef.id);
+    vmService.IsolateRef isolateRef = vm.isolates!.single;
+    await waitUntilIsolateIsRunnable(isolateRef.id!);
+    await serviceClient.resume(isolateRef.id!);
 
     Map<vmService.ClassRef, List<int>> instanceCounts =
         new Map<vmService.ClassRef, List<int>>();
@@ -76,12 +74,12 @@
       Map<vmService.ClassRef, vmService.Class> classInfo) {
     bool foundLeak = false;
     for (vmService.ClassRef c in instanceCounts.keys) {
-      List<int> listOfInstanceCounts = instanceCounts[c];
+      List<int> listOfInstanceCounts = instanceCounts[c]!;
 
       // Ignore VM internal stuff like "PatchClass", "PcDescriptors" etc.
       // (they don't have a url).
-      vmService.Class classDetails = classInfo[c];
-      String uriString = classDetails.location?.script?.uri;
+      vmService.Class classDetails = classInfo[c]!;
+      String? uriString = classDetails.location?.script?.uri;
       if (uriString == null) continue;
 
       // For now ignore anything not in package:kernel or package:front_end.
@@ -136,30 +134,30 @@
     try {
       while (true) {
         if (shouldBail(iterationNumber)) break;
-        if (!await waitUntilPaused(isolateRef.id)) break;
+        if (!await waitUntilPaused(isolateRef.id!)) break;
         print("\n\n====================\n\nIteration #$iterationNumber");
         iterationNumber++;
         vmService.AllocationProfile allocationProfile =
-            await forceGC(isolateRef.id);
-        for (vmService.ClassHeapStats member in allocationProfile.members) {
+            await forceGC(isolateRef.id!);
+        for (vmService.ClassHeapStats member in allocationProfile.members!) {
           if (!classInfo.containsKey(member.classRef)) {
-            vmService.Class c = await serviceClient.getObject(
-                isolateRef.id, member.classRef.id);
-            classInfo[member.classRef] = c;
+            vmService.Class c = (await serviceClient.getObject(
+                isolateRef.id!, member.classRef!.id!)) as vmService.Class;
+            classInfo[member.classRef!] = c;
           }
-          List<int> listOfInstanceCounts = instanceCounts[member.classRef];
+          List<int>? listOfInstanceCounts = instanceCounts[member.classRef];
           if (listOfInstanceCounts == null) {
-            listOfInstanceCounts = instanceCounts[member.classRef] = <int>[];
+            listOfInstanceCounts = instanceCounts[member.classRef!] = <int>[];
           }
           while (listOfInstanceCounts.length < iterationNumber - 2) {
             listOfInstanceCounts.add(0);
           }
-          listOfInstanceCounts.add(member.instancesCurrent);
+          listOfInstanceCounts.add(member.instancesCurrent!);
           if (listOfInstanceCounts.length != iterationNumber - 1) {
             throw "Unexpected length";
           }
         }
-        await serviceClient.resume(isolateRef.id);
+        await serviceClient.resume(isolateRef.id!);
       }
     } catch (e) {
       print("Got error: $e");
@@ -173,7 +171,7 @@
   }
 
   bool ignoredClass(vmService.Class classDetails) {
-    String uriString = classDetails.location?.script?.uri;
+    String? uriString = classDetails.location?.script?.uri;
     if (uriString == null) return true;
     if (uriString.startsWith("package:front_end/")) {
       // Classes used for lazy initialization will naturally fluctuate.
@@ -210,7 +208,7 @@
       // naturally increase, e.g. we can get 2 more booleans every time (up to
       // a maximum of 2 per library or however many would have been there if we
       // didn't canonicalize at all).
-      if (classDetails.name.endsWith("Constant")) return true;
+      if (classDetails.name!.endsWith("Constant")) return true;
 
       // These classes have proved to fluctuate, although the reason is less
       // clear.
diff --git a/pkg/front_end/test/incremental_dart2js_load_from_dill_test.dart b/pkg/front_end/test/incremental_dart2js_load_from_dill_test.dart
index f4b0342..329ca69 100644
--- a/pkg/front_end/test/incremental_dart2js_load_from_dill_test.dart
+++ b/pkg/front_end/test/incremental_dart2js_load_from_dill_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io' show Directory, File;
 
 import 'package:expect/expect.dart' show Expect;
@@ -20,7 +18,7 @@
 import 'incremental_suite.dart'
     show checkIsEqual, getOptions, initializedCompile, normalCompile;
 
-Directory outDir;
+late Directory outDir;
 
 Future<void> main() async {
   outDir =
@@ -64,10 +62,10 @@
         .readComponent(c);
     for (Uri uri in c.uriToSource.keys) {
       if (cSdk.uriToSource.containsKey(uri)) {
-        if ((c.uriToSource[uri].source?.length ?? 0) != 0) {
+        if (c.uriToSource[uri]!.source.length != 0) {
           throw "Compile contained sources for the sdk $uri";
         }
-        if ((c.uriToSource[uri].lineStarts?.length ?? 0) != 0) {
+        if ((c.uriToSource[uri]!.lineStarts?.length ?? 0) != 0) {
           throw "Compile contained line starts for the sdk $uri";
         }
       }
@@ -80,8 +78,8 @@
     [normalDill, true],
     [nonexisting, false],
   ]) {
-    Uri initializeWith = initializationData[0];
-    bool initializeExpect = initializationData[1];
+    Uri initializeWith = initializationData[0] as Uri;
+    bool initializeExpect = initializationData[1] as bool;
     stopwatch.reset();
     bool initializeResult = await initializedCompile(
         dart2jsUrl, fullDillFromInitialized, initializeWith, [invalidateUri],
@@ -135,16 +133,16 @@
         visitor.matchNamedNodes, visitor.checkNodes, 'procedures');
   }
 
-  bool _isMixinOrCloneReference(EquivalenceVisitor visitor, Reference a,
-      Reference b, String propertyName) {
+  bool _isMixinOrCloneReference(EquivalenceVisitor visitor, Reference? a,
+      Reference? b, String propertyName) {
     if (a != null && b != null) {
-      ReferenceName thisName = ReferenceName.fromReference(a);
-      ReferenceName otherName = ReferenceName.fromReference(b);
+      ReferenceName thisName = ReferenceName.fromReference(a)!;
+      ReferenceName otherName = ReferenceName.fromReference(b)!;
       if (thisName.kind == ReferenceNameKind.Member &&
           otherName.kind == ReferenceNameKind.Member &&
           thisName.memberName == otherName.memberName) {
-        String thisClassName = thisName.declarationName;
-        String otherClassName = otherName.declarationName;
+        String? thisClassName = thisName.declarationName;
+        String? otherClassName = otherName.declarationName;
         if (thisClassName != null &&
             otherClassName != null &&
             thisClassName.contains('&${otherClassName}')) {
diff --git a/pkg/front_end/test/incremental_dart2js_test.dart b/pkg/front_end/test/incremental_dart2js_test.dart
index 3a36586..d133437 100644
--- a/pkg/front_end/test/incremental_dart2js_test.dart
+++ b/pkg/front_end/test/incremental_dart2js_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import "incremental_dart2js_tester.dart";
 
 Future<void> main(List<String> args) async {
diff --git a/pkg/front_end/test/incremental_dart2js_tester.dart b/pkg/front_end/test/incremental_dart2js_tester.dart
index 3428af3..9ecdb51 100644
--- a/pkg/front_end/test/incremental_dart2js_tester.dart
+++ b/pkg/front_end/test/incremental_dart2js_tester.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import "dart:developer";
 import 'dart:io' show Platform;
 
@@ -49,9 +47,9 @@
   final int limit;
 
   Stopwatch stopwatch = new Stopwatch();
-  List<int> firstCompileData;
-  Map<Uri, List<int>> libToData;
-  List<Uri> uris;
+  late List<int> firstCompileData;
+  late Map<Uri, List<int>> libToData;
+  late List<Uri> uris;
 
   List<Uri> diffs = <Uri>[];
   Set<Uri> componentUris = new Set<Uri>();
@@ -136,7 +134,7 @@
 
         List<int> libSerialized =
             serializeComponent(c2, filter: (l) => l == library);
-        if (!isEqual(libToData[library.importUri], libSerialized)) {
+        if (!isEqual(libToData[library.importUri]!, libSerialized)) {
           print("=====");
           print("=====");
           print("=====");
@@ -182,7 +180,7 @@
         .alternativeInvalidationStrategy] = useExperimentalInvalidation;
     helper.TestIncrementalCompiler compiler =
         new helper.TestIncrementalCompiler(options, input);
-    Component c = await compiler.computeDelta();
+    Component? c = await compiler.computeDelta();
     print("Compiled dart2js to Component with ${c.libraries.length} libraries "
         "in ${stopwatch.elapsedMilliseconds} ms.");
     stopwatch.reset();
@@ -213,8 +211,9 @@
     stopwatch.reset();
 
     uris = c.uriToSource.values
-        .map((s) => s != null ? s.importUri : null)
-        .where((u) => u != null && u.scheme != "dart")
+        .map((s) => s.importUri)
+        .whereType<Uri>()
+        .where((u) => u.scheme != "dart")
         .toSet()
         .toList();
 
diff --git a/pkg/front_end/test/incremental_flutter_tester.dart b/pkg/front_end/test/incremental_flutter_tester.dart
index 3663d30..fc01452 100644
--- a/pkg/front_end/test/incremental_flutter_tester.dart
+++ b/pkg/front_end/test/incremental_flutter_tester.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io' show Directory, File, exit;
 
 import 'package:front_end/src/api_prototype/compiler_options.dart'
@@ -32,7 +30,7 @@
 
 import "incremental_utils.dart" as util;
 
-void usage(String extraMessage) {
+Never usage(String extraMessage) {
   print("""Usage as something like:
     out/ReleaseX64/dart pkg/front_end/test/incremental_flutter_tester.dart \
       --fast --experimental \
@@ -49,8 +47,8 @@
 Future<void> main(List<String> args) async {
   bool fast = false;
   bool useExperimentalInvalidation = false;
-  File inputFile;
-  Directory flutterPatchedSdk;
+  File? inputFile;
+  Directory? flutterPatchedSdk;
   for (String arg in args) {
     if (arg == "--fast") {
       fast = true;
@@ -84,12 +82,12 @@
       .alternativeInvalidationStrategy] = useExperimentalInvalidation;
   helper.TestIncrementalCompiler compiler =
       new helper.TestIncrementalCompiler(options, inputFile.uri);
-  Component c = await compiler.computeDelta();
+  Component? c = await compiler.computeDelta();
   print("Compiled to Component with ${c.libraries.length} "
       "libraries in ${stopwatch.elapsedMilliseconds} ms.");
   stopwatch.reset();
-  List<int> firstCompileData;
-  Map<Uri, List<int>> libToData;
+  late List<int> firstCompileData;
+  late Map<Uri, List<int>> libToData;
   if (fast) {
     libToData = {};
     c.libraries.sort((l1, l2) {
@@ -117,8 +115,9 @@
   stopwatch.reset();
 
   List<Uri> uris = c.uriToSource.values
-      .map((s) => s != null ? s.importUri : null)
-      .where((u) => u != null && u.scheme != "dart")
+      .map((s) => s.importUri)
+      .whereType<Uri>()
+      .where((u) => u.scheme != "dart")
       .toSet()
       .toList();
 
@@ -179,7 +178,7 @@
 
         List<int> libSerialized =
             serializeComponent(c2, filter: (l) => l == library);
-        if (!isEqual(libToData[library.importUri], libSerialized)) {
+        if (!isEqual(libToData[library.importUri]!, libSerialized)) {
           print("=====");
           print("=====");
           print("=====");
@@ -285,12 +284,12 @@
 
 class PrinterPrime extends Printer {
   PrinterPrime(StringSink sink,
-      {NameSystem syntheticNames,
+      {NameSystem? syntheticNames,
       bool showOffsets: false,
       bool showMetadata: false,
-      ImportTable importTable,
-      Annotator annotator,
-      Map<String, MetadataRepository<Object>> metadata})
+      ImportTable? importTable,
+      Annotator? annotator,
+      Map<String, MetadataRepository<dynamic>>? metadata})
       : super(sink,
             showOffsets: showOffsets,
             showMetadata: showMetadata,
@@ -300,7 +299,7 @@
 
   @override
   PrinterPrime createInner(ImportTable importTable,
-      Map<String, MetadataRepository<Object>> metadata) {
+      Map<String, MetadataRepository<dynamic>>? metadata) {
     return new PrinterPrime(sink,
         importTable: importTable,
         metadata: metadata,
@@ -311,7 +310,7 @@
   }
 
   @override
-  void writeInterfaceTarget(Name name, Reference target) {
+  void writeInterfaceTarget(Name name, Reference? target) {
     // Skipped!
   }
 }
diff --git a/pkg/front_end/test/incremental_load_from_invalid_dill_test.dart b/pkg/front_end/test/incremental_load_from_invalid_dill_test.dart
index ac40f6e..3d96399 100644
--- a/pkg/front_end/test/incremental_load_from_invalid_dill_test.dart
+++ b/pkg/front_end/test/incremental_load_from_invalid_dill_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io' show File;
 
 import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart'
@@ -57,21 +55,21 @@
 }
 
 class Tester {
-  Uri sdkRoot;
-  Uri base;
-  Uri sdkSummary;
-  Uri initializeFrom;
-  Uri helperFile;
-  Uri helper2File;
-  Uri entryPoint;
-  Uri entryPointImportDartFoo;
-  Uri platformUri;
-  List<int> sdkSummaryData;
-  List<DiagnosticMessage> errorMessages;
-  List<DiagnosticMessage> warningMessages;
-  MemoryFileSystem fs;
-  CompilerOptions options;
-  IncrementalCompiler compiler;
+  late Uri sdkRoot;
+  late Uri base;
+  late Uri sdkSummary;
+  late Uri initializeFrom;
+  late Uri helperFile;
+  late Uri helper2File;
+  late Uri entryPoint;
+  late Uri entryPointImportDartFoo;
+  late Uri platformUri;
+  late List<int> sdkSummaryData;
+  late List<DiagnosticMessage> errorMessages;
+  late List<DiagnosticMessage> warningMessages;
+  late MemoryFileSystem fs;
+  late CompilerOptions options;
+  late IncrementalCompiler compiler;
 
   Future<void> compileExpectInitializeFailAndSpecificWarning(
       Code expectedWarningCode, bool writeFileOnCrashReport) async {
@@ -248,7 +246,7 @@
     List<int> mixedPart1;
     {
       // Create a component that is compiled without NNBD.
-      Map<ExperimentalFlag, bool> prevTesting =
+      Map<ExperimentalFlag, bool>? prevTesting =
           options.defaultExperimentFlagsForTesting;
       options.defaultExperimentFlagsForTesting = {
         ExperimentalFlag.nonNullable: false
@@ -270,7 +268,7 @@
     List<int> mixedPart2;
     {
       // Create a component that is compiled with strong NNBD.
-      Map<ExperimentalFlag, bool> prevTesting =
+      Map<ExperimentalFlag, bool>? prevTesting =
           options.defaultExperimentFlagsForTesting;
       options.defaultExperimentFlagsForTesting = {
         ExperimentalFlag.nonNullable: true
@@ -302,7 +300,7 @@
 
 class DeleteTempFilesIncrementalCompiler extends IncrementalCompiler {
   DeleteTempFilesIncrementalCompiler(CompilerContext context,
-      [Uri initializeFromDillUri])
+      [Uri? initializeFromDillUri])
       : super(context, initializeFromDillUri);
 
   @override
diff --git a/pkg/front_end/test/issue_34856_test.dart b/pkg/front_end/test/issue_34856_test.dart
index a82d5c5..a0b5fc1 100644
--- a/pkg/front_end/test/issue_34856_test.dart
+++ b/pkg/front_end/test/issue_34856_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io' show File;
 
 import 'package:async_helper/async_helper.dart' show asyncTest;
@@ -68,14 +66,14 @@
   fs.entityForUri(platformDill).writeAsBytesSync(platformDillBytes);
   fs
       .entityForUri(base.resolve("lib.dart"))
-      .writeAsStringSync(files["lib.dart"]);
+      .writeAsStringSync(files["lib.dart"]!);
   CompilerOptions options = new CompilerOptions()
     ..fileSystem = fs
     ..sdkSummary = platformDill;
 
   Component component =
       (await kernelForModule(<Uri>[base.resolve("lib.dart")], options))
-          .component;
+          .component!;
 
   fs = new MemoryFileSystem(base);
   fs.entityForUri(platformDill).writeAsBytesSync(platformDillBytes);
@@ -84,7 +82,7 @@
       .writeAsBytesSync(serializeComponent(component));
   fs
       .entityForUri(base.resolve("repro.dart"))
-      .writeAsStringSync(files["repro.dart"]);
+      .writeAsStringSync(files["repro.dart"]!);
 
   options = new CompilerOptions()
     ..fileSystem = fs
@@ -94,12 +92,12 @@
 
   List<Uri> inputs = <Uri>[base.resolve("repro.dart")];
 
-  component = (await kernelForModule(inputs, options)).component;
+  component = (await kernelForModule(inputs, options)).component!;
 
   List<Object> errors = await CompilerContext.runWithOptions(
       new ProcessedOptions(options: options, inputs: inputs),
       (_) => new Future<List<Object>>.value(
-          verifyComponent(component, options.target, skipPlatform: true)));
+          verifyComponent(component, options.target!, skipPlatform: true)));
 
   serializeComponent(component);
 
diff --git a/pkg/front_end/test/kernel_generator_test.dart b/pkg/front_end/test/kernel_generator_test.dart
index 275df00..4c0bb6d 100644
--- a/pkg/front_end/test/kernel_generator_test.dart
+++ b/pkg/front_end/test/kernel_generator_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:kernel/ast.dart'
     show EmptyStatement, Component, ReturnStatement, StaticInvocation;
 
@@ -45,7 +43,7 @@
         ..compileSdk = true // To prevent FE from loading an sdk-summary.
         ..onDiagnostic = errors.add;
 
-      Component component =
+      Component? component =
           (await compileScript('main() => print("hi");', options: options))
               ?.component;
       expect(component, isNotNull);
@@ -59,7 +57,7 @@
             Uri.parse('org-dartlang-test:///not_existing_summary_file')
         ..onDiagnostic = errors.add;
 
-      Component component =
+      Component? component =
           (await compileScript('main() => print("hi");', options: options))
               ?.component;
       expect(component, isNotNull);
@@ -74,14 +72,14 @@
         // sources of the sdk directly.
         ..librariesSpecificationUri = invalidCoreLibsSpecUri;
       Component component =
-          (await compileScript('main() => print("hi");', options: options))
-              ?.component;
+          (await compileScript('main() => print("hi");', options: options))!
+              .component!;
       var core = component.libraries.firstWhere(isDartCoreLibrary);
       var printMember = core.members.firstWhere((m) => m.name.text == 'print');
 
       // Note: summaries created by the SDK today contain empty statements as
       // method bodies.
-      expect(printMember.function.body is! EmptyStatement, isTrue);
+      expect(printMember.function!.body is! EmptyStatement, isTrue);
     });
 
     test('compiler requires a main method', () async {
@@ -95,8 +93,8 @@
     test('generated program contains source-info', () async {
       Component component = (await compileScript(
               'a() => print("hi"); main() {}',
-              fileName: 'a.dart'))
-          ?.component;
+              fileName: 'a.dart'))!
+          .component!;
       // Kernel always store an empty '' key in the map, so there is always at
       // least one. Having more means that source-info is added.
       expect(component.uriToSource.keys.length, greaterThan(1));
@@ -139,13 +137,13 @@
 
       var unitA = await compileUnit(['a.dart'], sources);
       // Pretend that the compiled code is a summary
-      sources['a.dill'] = serializeComponent(unitA);
+      sources['a.dill'] = serializeComponent(unitA!);
 
       var unitBC = await compileUnit(['b.dart', 'c.dart'], sources,
           additionalDills: ['a.dill']);
 
       // Pretend that the compiled code is a summary
-      sources['bc.dill'] = serializeComponent(unitBC);
+      sources['bc.dill'] = serializeComponent(unitBC!);
 
       void checkDCallsC(Component component) {
         var dLib = findLibrary(component, 'd.dart');
@@ -160,11 +158,11 @@
 
       var unitD1 = await compileUnit(['d.dart'], sources,
           additionalDills: ['a.dill', 'bc.dill']);
-      checkDCallsC(unitD1);
+      checkDCallsC(unitD1!);
 
       var unitD2 = await compileUnit(['d.dart'], sources,
           additionalDills: ['bc.dill', 'a.dill']);
-      checkDCallsC(unitD2);
+      checkDCallsC(unitD2!);
     });
 
     // TODO(sigmund): add tests with trimming dependencies
diff --git a/pkg/front_end/test/lint_suite.dart b/pkg/front_end/test/lint_suite.dart
index 130a452..18d7e28 100644
--- a/pkg/front_end/test/lint_suite.dart
+++ b/pkg/front_end/test/lint_suite.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io' show Directory, File, FileSystemEntity;
 
 import 'dart:typed_data' show Uint8List;
@@ -58,10 +56,10 @@
   }
 
   String getErrorMessage(int offset, int squigglyLength, String message) {
-    cache.source ??= new Source(cache.lineStarts, cache.rawBytes, uri, uri);
-    Location location = cache.source.getLocation(uri, offset);
+    cache.source ??= new Source(cache.lineStarts, cache.rawBytes!, uri, uri);
+    Location location = cache.source!.getLocation(uri, offset);
     return command_line_reporting.formatErrorMessage(
-        cache.source.getTextLine(location.line),
+        cache.source!.getTextLine(location.line),
         location,
         squigglyLength,
         uri.toString(),
@@ -70,16 +68,16 @@
 }
 
 class LintTestCache {
-  List<int> rawBytes;
-  List<int> lineStarts;
-  Source source;
-  Token firstToken;
-  PackageConfig packages;
+  List<int>? rawBytes;
+  late List<int> lineStarts;
+  Source? source;
+  Token? firstToken;
+  PackageConfig? packages;
 }
 
 class Context extends ChainContext {
   final bool onlyInGit;
-  Context({this.onlyInGit});
+  Context({required this.onlyInGit});
 
   @override
   final List<Step> steps = const <Step>[
@@ -95,7 +93,7 @@
 
   @override
   Stream<LintTestDescription> list(Chain suite) async* {
-    Set<Uri> gitFiles;
+    late Set<Uri> gitFiles;
     if (onlyInGit) {
       gitFiles = await getGitFiles(suite.uri);
     }
@@ -160,9 +158,9 @@
       File f = new File.fromUri(description.uri);
       description.cache.rawBytes = f.readAsBytesSync();
 
-      Uint8List bytes = new Uint8List(description.cache.rawBytes.length + 1);
+      Uint8List bytes = new Uint8List(description.cache.rawBytes!.length + 1);
       bytes.setRange(
-          0, description.cache.rawBytes.length, description.cache.rawBytes);
+          0, description.cache.rawBytes!.length, description.cache.rawBytes!);
 
       Utf8BytesScanner scanner =
           new Utf8BytesScanner(bytes, includeComments: true);
@@ -195,7 +193,7 @@
 
     Parser parser = new Parser(description.listener,
         useImplicitCreationExpression: useImplicitCreationExpressionInCfe);
-    parser.parseUnit(description.cache.firstToken);
+    parser.parseUnit(description.cache.firstToken!);
 
     if (description.listener.problems.isEmpty) {
       return pass(description);
@@ -206,9 +204,9 @@
 
 class LintListener extends Listener {
   List<String> problems = <String>[];
-  LintTestDescription description;
+  late final LintTestDescription description;
   @override
-  Uri uri;
+  late final Uri uri;
 
   void onProblem(int offset, int squigglyLength, String message) {
     problems.add(description.getErrorMessage(offset, squigglyLength, message));
@@ -220,15 +218,15 @@
 
   @override
   void beginVariablesDeclaration(
-      Token token, Token lateToken, Token varFinalOrConst) {
+      Token token, Token? lateToken, Token? varFinalOrConst) {
     if (!_latestTypes.last.type) {
       onProblem(
-          varFinalOrConst.offset, varFinalOrConst.length, "No explicit type.");
+          varFinalOrConst!.offset, varFinalOrConst.length, "No explicit type.");
     }
   }
 
   @override
-  void handleType(Token beginToken, Token questionMark) {
+  void handleType(Token beginToken, Token? questionMark) {
     _latestTypes.add(new LatestType(beginToken, true));
   }
 
@@ -238,17 +236,17 @@
   }
 
   @override
-  void endFunctionType(Token functionToken, Token questionMark) {
+  void endFunctionType(Token functionToken, Token? questionMark) {
     _latestTypes.add(new LatestType(functionToken, true));
   }
 
   @override
   void endTopLevelFields(
-      Token externalToken,
-      Token staticToken,
-      Token covariantToken,
-      Token lateToken,
-      Token varFinalOrConst,
+      Token? externalToken,
+      Token? staticToken,
+      Token? covariantToken,
+      Token? lateToken,
+      Token? varFinalOrConst,
       int count,
       Token beginToken,
       Token endToken) {
@@ -260,29 +258,29 @@
 
   @override
   void endClassFields(
-      Token abstractToken,
-      Token externalToken,
-      Token staticToken,
-      Token covariantToken,
-      Token lateToken,
-      Token varFinalOrConst,
+      Token? abstractToken,
+      Token? externalToken,
+      Token? staticToken,
+      Token? covariantToken,
+      Token? lateToken,
+      Token? varFinalOrConst,
       int count,
       Token beginToken,
       Token endToken) {
     if (!_latestTypes.last.type) {
       onProblem(
-          varFinalOrConst.offset, varFinalOrConst.length, "No explicit type.");
+          varFinalOrConst!.offset, varFinalOrConst.length, "No explicit type.");
     }
     _latestTypes.removeLast();
   }
 
   @override
   void endFormalParameter(
-      Token thisKeyword,
-      Token periodAfterThis,
+      Token? thisKeyword,
+      Token? periodAfterThis,
       Token nameToken,
-      Token initializerStart,
-      Token initializerEnd,
+      Token? initializerStart,
+      Token? initializerEnd,
       FormalParameterKind kind,
       MemberKind memberKind) {
     _latestTypes.removeLast();
@@ -300,8 +298,8 @@
   Set<Uri> seenImports = new Set<Uri>();
 
   @override
-  void endImport(Token importKeyword, Token semicolon) {
-    Token importUriToken = importKeyword.next;
+  void endImport(Token importKeyword, Token? semicolon) {
+    Token importUriToken = importKeyword.next!;
     String importUri = importUriToken.lexeme;
     if (importUri.startsWith("r")) {
       importUri = importUri.substring(2, importUri.length - 1);
@@ -311,7 +309,7 @@
     Uri resolved = uri.resolve(importUri);
     if (resolved.scheme == "package") {
       if (description.cache.packages != null) {
-        resolved = description.cache.packages.resolve(resolved);
+        resolved = description.cache.packages!.resolve(resolved)!;
       }
     }
     if (!seenImports.add(resolved)) {
@@ -324,7 +322,7 @@
 class ExportsLintListener extends LintListener {
   @override
   void endExport(Token exportKeyword, Token semicolon) {
-    Token exportUriToken = exportKeyword.next;
+    Token exportUriToken = exportKeyword.next!;
     String exportUri = exportUriToken.lexeme;
     if (exportUri.startsWith("r")) {
       exportUri = exportUri.substring(2, exportUri.length - 1);
@@ -334,7 +332,7 @@
     Uri resolved = uri.resolve(exportUri);
     if (resolved.scheme == "package") {
       if (description.cache.packages != null) {
-        resolved = description.cache.packages.resolve(resolved);
+        resolved = description.cache.packages!.resolve(resolved)!;
       }
     }
     onProblem(exportUriToken.offset, exportUriToken.lexeme.length,
diff --git a/pkg/front_end/test/member_covariance_test.dart b/pkg/front_end/test/member_covariance_test.dart
index 93ff6e4..a9af238 100644
--- a/pkg/front_end/test/member_covariance_test.dart
+++ b/pkg/front_end/test/member_covariance_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:expect/expect.dart';
 import 'package:kernel/ast.dart';
 import 'package:front_end/src/fasta/kernel/member_covariance.dart';
diff --git a/pkg/front_end/test/memory_file_system_test.dart b/pkg/front_end/test/memory_file_system_test.dart
index 5c20d45..eb635c7 100644
--- a/pkg/front_end/test/memory_file_system_test.dart
+++ b/pkg/front_end/test/memory_file_system_test.dart
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 // SharedOptions=--supermixin
 
-// @dart = 2.9
-
 library front_end.test.memory_file_system_test;
 
 import 'dart:convert';
@@ -32,8 +30,8 @@
 
 @reflectiveTest
 class FileTest extends _BaseTestNative {
-  String path;
-  MemoryFileSystemEntity file;
+  late String path;
+  late MemoryFileSystemEntity file;
 
   void setUp() {
     _baseSetUp();
@@ -180,7 +178,7 @@
 }
 
 abstract class MemoryFileSystemTestMixin implements _BaseTest {
-  Uri tempUri;
+  late Uri tempUri;
 
   void setUp() {
     _baseSetUp();
@@ -287,13 +285,15 @@
 class _BaseTestNative extends _BaseTest {
   @override
   final pathos.Context context = pathos.context;
-  @override
-  MemoryFileSystem fileSystem;
-  @override
-  String tempPath;
 
   @override
-  String join(String path1, String path2, [String path3, String path4]) =>
+  late MemoryFileSystem fileSystem;
+
+  @override
+  late String tempPath;
+
+  @override
+  String join(String path1, String path2, [String? path3, String? path4]) =>
       pathos.join(path1, path2, path3, path4);
 
   @override
@@ -306,13 +306,15 @@
 class _BaseTestPosix extends _BaseTest {
   @override
   final pathos.Context context = pathos.posix;
-  @override
-  MemoryFileSystem fileSystem;
-  @override
-  String tempPath;
 
   @override
-  String join(String path1, String path2, [String path3, String path4]) =>
+  late MemoryFileSystem fileSystem;
+
+  @override
+  late String tempPath;
+
+  @override
+  String join(String path1, String path2, [String? path3, String? path4]) =>
       pathos.posix.join(path1, path2, path3, path4);
 
   @override
@@ -325,13 +327,15 @@
 class _BaseTestWindows extends _BaseTest {
   @override
   final pathos.Context context = pathos.windows;
-  @override
-  MemoryFileSystem fileSystem;
-  @override
-  String tempPath;
 
   @override
-  String join(String path1, String path2, [String path3, String path4]) =>
+  late MemoryFileSystem fileSystem;
+
+  @override
+  late String tempPath;
+
+  @override
+  String join(String path1, String path2, [String? path3, String? path4]) =>
       pathos.windows.join(path1, path2, path3, path4);
 
   @override
diff --git a/pkg/front_end/test/messages_json_test.dart b/pkg/front_end/test/messages_json_test.dart
index 8df7698..b86e4b9 100644
--- a/pkg/front_end/test/messages_json_test.dart
+++ b/pkg/front_end/test/messages_json_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart'
     show DiagnosticMessage, getMessageUri;
 
@@ -24,17 +22,17 @@
     Severity severity = Severity.values[i];
     Code code = new Code("MyCodeName");
     Message message = new Message(code, problemMessage: '');
-    LocatedMessage locatedMessage1 =
+    LocatedMessage locatedMessage =
         new LocatedMessage(Uri.parse("what:ever/fun_1.dart"), 117, 2, message);
     FormattedMessage formattedMessage2 = new FormattedMessage(
-        null,
+        locatedMessage,
         "Formatted string Plain #2",
         "Formatted string Colorized #2",
         13,
         2,
         Severity.error, []);
     FormattedMessage formattedMessage3 = new FormattedMessage(
-        null,
+        locatedMessage,
         "Formatted string Plain #3",
         "Formatted string Colorized #3",
         313,
@@ -42,7 +40,7 @@
         Severity.error, []);
 
     FormattedMessage formattedMessage1 = new FormattedMessage(
-        locatedMessage1,
+        locatedMessage,
         "Formatted string Plain",
         "Formatted string Colorized",
         42,
@@ -90,20 +88,20 @@
   expect(a.severity, b.severity);
   expect(getMessageUri(a), getMessageUri(b));
 
-  List<Uri> uriList1 = a.involvedFiles?.toList();
-  List<Uri> uriList2 = b.involvedFiles?.toList();
+  List<Uri>? uriList1 = a.involvedFiles?.toList();
+  List<Uri>? uriList2 = b.involvedFiles?.toList();
   expect(uriList1?.length, uriList2?.length);
   if (uriList1 != null) {
     for (int i = 0; i < uriList1.length; i++) {
-      expect(uriList1[i], uriList2[i]);
+      expect(uriList1[i], uriList2![i]);
     }
   }
 
-  String string1 = a.codeName;
-  String string2 = b.codeName;
+  String? string1 = a.codeName;
+  String? string2 = b.codeName;
   expect(string1, string2);
 }
 
-void expect(Object actual, Object expect) {
+void expect(Object? actual, Object? expect) {
   if (expect != actual) throw "Expected $expect got $actual";
 }
diff --git a/pkg/front_end/test/mixin_export_test.dart b/pkg/front_end/test/mixin_export_test.dart
index ff1f579..5fafac7 100644
--- a/pkg/front_end/test/mixin_export_test.dart
+++ b/pkg/front_end/test/mixin_export_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:async_helper/async_helper.dart' show asyncTest;
 
 import 'package:front_end/src/testing/compiler_common.dart';
diff --git a/pkg/front_end/test/multiple_simultaneous_compiles_test.dart b/pkg/front_end/test/multiple_simultaneous_compiles_test.dart
index df42515..f2ea8f96 100644
--- a/pkg/front_end/test/multiple_simultaneous_compiles_test.dart
+++ b/pkg/front_end/test/multiple_simultaneous_compiles_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io' show File, Platform;
 
 import 'package:front_end/src/base/processed_options.dart'
@@ -27,7 +25,7 @@
   List<Future> futures = [];
   List<int> compilesLeft = new List<int>.filled(5, 8);
   for (int i = 0; i < compilesLeft.length; i++) {
-    Future<Component> compileAgain() async {
+    Future<Component?> compileAgain() async {
       print("$i has ${compilesLeft[i]} left.");
       if (compilesLeft[i] > 0) {
         compilesLeft[i]--;
@@ -56,13 +54,13 @@
       "(with the same compiler) (without crashing)");
 }
 
-List<IncrementalCompiler> compilers = [];
+List<IncrementalCompiler?> compilers = [];
 
 Future<Component> compile(int compilerNum, Uri uri) async {
   if (compilers.length <= compilerNum) {
     compilers.length = compilerNum + 1;
   }
-  IncrementalCompiler compiler = compilers[compilerNum];
+  IncrementalCompiler? compiler = compilers[compilerNum];
   if (compiler == null) {
     var options = getOptions();
     compiler = new IncrementalCompiler(new CompilerContext(
diff --git a/pkg/front_end/test/packages_format_error_test.dart b/pkg/front_end/test/packages_format_error_test.dart
index a9fa5f3..a19551f 100644
--- a/pkg/front_end/test/packages_format_error_test.dart
+++ b/pkg/front_end/test/packages_format_error_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart'
     show DiagnosticMessage, getMessageCodeObject;
 
diff --git a/pkg/front_end/test/parser_all_suite.dart b/pkg/front_end/test/parser_all_suite.dart
index 9aab3a5..bef7873 100644
--- a/pkg/front_end/test/parser_all_suite.dart
+++ b/pkg/front_end/test/parser_all_suite.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:testing/testing.dart' show Chain, ChainContext, runMe;
 
 import 'parser_suite.dart';
diff --git a/pkg/front_end/test/parser_test_listener_creator.dart b/pkg/front_end/test/parser_test_listener_creator.dart
index c2c6df4..3ea90c1 100644
--- a/pkg/front_end/test/parser_test_listener_creator.dart
+++ b/pkg/front_end/test/parser_test_listener_creator.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.9
-
 import 'dart:io' show File;
 import 'dart:typed_data' show Uint8List;
 
@@ -120,15 +118,15 @@
 class ParserCreatorListener extends Listener {
   final StringSink out;
   bool insideListenerClass = false;
-  String currentMethodName;
-  String latestSeenParameterTypeToken;
-  List<String> parameters = <String>[];
-  List<String> parameterTypes = <String>[];
+  String? currentMethodName;
+  String? latestSeenParameterTypeToken;
+  List<String> parameters = [];
+  List<String?> parameterTypes = [];
 
   ParserCreatorListener(this.out);
 
   @override
-  void beginClassDeclaration(Token begin, Token abstractToken, Token name) {
+  void beginClassDeclaration(Token begin, Token? abstractToken, Token name) {
     if (name.lexeme == "Listener") insideListenerClass = true;
   }
 
@@ -140,23 +138,23 @@
   @override
   void beginMethod(
       DeclarationKind declarationKind,
-      Token externalToken,
-      Token staticToken,
-      Token covariantToken,
-      Token varFinalOrConst,
-      Token getOrSet,
+      Token? externalToken,
+      Token? staticToken,
+      Token? covariantToken,
+      Token? varFinalOrConst,
+      Token? getOrSet,
       Token name) {
     currentMethodName = name.lexeme;
   }
 
   @override
-  void endClassMethod(Token getOrSet, Token beginToken, Token beginParam,
-      Token beginInitializers, Token endToken) {
+  void endClassMethod(Token? getOrSet, Token beginToken, Token beginParam,
+      Token? beginInitializers, Token endToken) {
     if (insideListenerClass) {
       out.writeln("  @override");
       out.write("  ");
       Token token = beginToken;
-      Token latestToken;
+      Token? latestToken;
       while (true) {
         if (latestToken != null && latestToken.charEnd < token.charOffset) {
           out.write(" ");
@@ -171,14 +169,14 @@
           throw token.runtimeType;
         }
         latestToken = token;
-        token = token.next;
+        token = token.next!;
       }
 
       if (token is SimpleToken && token.type == TokenType.FUNCTION) {
         out.write(" null;");
       } else {
         out.write("\n    ");
-        if (currentMethodName.startsWith("end")) {
+        if (currentMethodName!.startsWith("end")) {
           out.write("indent--;\n    ");
         }
         for (int i = 0; i < parameterTypes.length; i++) {
@@ -196,7 +194,7 @@
         }
         out.write(")');\n  ");
 
-        if (currentMethodName.startsWith("begin")) {
+        if (currentMethodName!.startsWith("begin")) {
           out.write("  indent++;\n  ");
         }
 
@@ -224,17 +222,17 @@
   }
 
   @override
-  void handleType(Token beginToken, Token questionMark) {
+  void handleType(Token beginToken, Token? questionMark) {
     latestSeenParameterTypeToken = beginToken.lexeme;
   }
 
   @override
   void endFormalParameter(
-      Token thisKeyword,
-      Token periodAfterThis,
+      Token? thisKeyword,
+      Token? periodAfterThis,
       Token nameToken,
-      Token initializerStart,
-      Token initializerEnd,
+      Token? initializerStart,
+      Token? initializerEnd,
       FormalParameterKind kind,
       MemberKind memberKind) {
     parameters.add(nameToken.lexeme);
diff --git a/pkg/front_end/test/parser_test_parser_creator.dart b/pkg/front_end/test/parser_test_parser_creator.dart
index 136e903..a23ad8a 100644
--- a/pkg/front_end/test/parser_test_parser_creator.dart
+++ b/pkg/front_end/test/parser_test_parser_creator.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io';
 import 'dart:typed_data';
 
@@ -106,14 +104,14 @@
 class ParserCreatorListener extends Listener {
   final StringSink out;
   bool insideParserClass = false;
-  String currentMethodName;
-  List<String> parameters = <String>[];
-  List<String> parametersNamed = <String>[];
+  String? currentMethodName;
+  List<String> parameters = [];
+  List<String?> parametersNamed = [];
 
   ParserCreatorListener(this.out);
 
   @override
-  void beginClassDeclaration(Token begin, Token abstractToken, Token name) {
+  void beginClassDeclaration(Token begin, Token? abstractToken, Token name) {
     if (name.lexeme == "Parser") insideParserClass = true;
   }
 
@@ -125,29 +123,29 @@
   @override
   void beginMethod(
       DeclarationKind declarationKind,
-      Token externalToken,
-      Token staticToken,
-      Token covariantToken,
-      Token varFinalOrConst,
-      Token getOrSet,
+      Token? externalToken,
+      Token? staticToken,
+      Token? covariantToken,
+      Token? varFinalOrConst,
+      Token? getOrSet,
       Token name) {
     currentMethodName = name.lexeme;
   }
 
   @override
-  void endClassConstructor(Token getOrSet, Token beginToken, Token beginParam,
-      Token beginInitializers, Token endToken) {
+  void endClassConstructor(Token? getOrSet, Token beginToken, Token beginParam,
+      Token? beginInitializers, Token endToken) {
     parameters.clear();
     parametersNamed.clear();
     currentMethodName = null;
   }
 
   @override
-  void endClassMethod(Token getOrSet, Token beginToken, Token beginParam,
-      Token beginInitializers, Token endToken) {
-    if (insideParserClass && !currentMethodName.startsWith("_")) {
+  void endClassMethod(Token? getOrSet, Token beginToken, Token beginParam,
+      Token? beginInitializers, Token endToken) {
+    if (insideParserClass && !currentMethodName!.startsWith("_")) {
       Token token = beginToken;
-      Token latestToken;
+      Token? latestToken;
       out.writeln("  @override");
       out.write("  ");
       while (true) {
@@ -156,7 +154,7 @@
             out.write(" ");
           }
           out.write("dynamic");
-          token = troubleParameterTokens[token];
+          token = troubleParameterTokens[token]!;
         }
         if (latestToken != null && latestToken.charEnd < token.charOffset) {
           out.write(" ");
@@ -169,16 +167,15 @@
         out.write(token.lexeme);
         if (token is BeginToken &&
             token.type == TokenType.OPEN_CURLY_BRACKET &&
-            (beginParam == null ||
-                beginParam.endGroup == endToken ||
-                token.charOffset > beginParam.endGroup.charOffset)) {
+            (beginParam.endGroup == endToken ||
+                token.charOffset > beginParam.endGroup!.charOffset)) {
           break;
         }
         if (token == endToken) {
           throw token.runtimeType;
         }
         latestToken = token;
-        token = token.next;
+        token = token.next!;
       }
 
       out.write("\n    ");
@@ -244,17 +241,17 @@
     formalParametersNestLevel--;
   }
 
-  Token currentFormalParameterToken;
+  Token? currentFormalParameterToken;
 
   @override
-  void beginFormalParameter(Token token, MemberKind kind, Token requiredToken,
-      Token covariantToken, Token varFinalOrConst) {
+  void beginFormalParameter(Token token, MemberKind kind, Token? requiredToken,
+      Token? covariantToken, Token? varFinalOrConst) {
     if (formalParametersNestLevel == 1) {
       currentFormalParameterToken = token;
     }
   }
 
-  Map<Token, Token> troubleParameterTokens = {};
+  Map<Token?, Token?> troubleParameterTokens = {};
 
   @override
   void handleIdentifier(Token token, IdentifierContext context) {
@@ -265,11 +262,11 @@
 
   @override
   void endFormalParameter(
-      Token thisKeyword,
-      Token periodAfterThis,
+      Token? thisKeyword,
+      Token? periodAfterThis,
       Token nameToken,
-      Token initializerStart,
-      Token initializerEnd,
+      Token? initializerStart,
+      Token? initializerEnd,
       FormalParameterKind kind,
       MemberKind memberKind) {
     if (formalParametersNestLevel != 1) {
diff --git a/pkg/front_end/test/precedence_info_test.dart b/pkg/front_end/test/precedence_info_test.dart
index cce5241..0508cc8 100644
--- a/pkg/front_end/test/precedence_info_test.dart
+++ b/pkg/front_end/test/precedence_info_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:_fe_analyzer_shared/src/scanner/scanner.dart';
 import 'package:_fe_analyzer_shared/src/scanner/abstract_scanner.dart'
     show AbstractScanner;
@@ -22,11 +20,11 @@
 @reflectiveTest
 class PrecedenceInfoTest {
   void assertInfo(check(String source, Token token)) {
-    void assertLexeme(String source) {
+    void assertLexeme(String? source) {
       if (source == null || source.isEmpty) return;
       var token = scanString(source, includeComments: true).tokens;
       while (token is ErrorToken) {
-        token = token.next;
+        token = token.next!;
       }
       check(source, token);
     }
@@ -290,11 +288,11 @@
   }
 
   void test_name() {
-    void assertName(String source, String name, {int offset: 0}) {
+    void assertName(String? source, String name, {int offset: 0}) {
       if (source == null || source.isEmpty) return;
       var token = scanString(source, includeComments: true).tokens;
       while (token is ErrorToken || token.offset < offset) {
-        token = token.next;
+        token = token.next!;
       }
       expect(token.type.name, name,
           reason: 'source: $source\ntoken: ${token.lexeme}');
@@ -393,7 +391,7 @@
       for (String source in lexemes) {
         var token = scanString(source, includeComments: true).tokens;
         while (token is ErrorToken) {
-          token = token.next;
+          token = token.next!;
         }
         expect(token.type.precedence, precedence, reason: source);
       }
diff --git a/pkg/front_end/test/read_dill_from_binary_md_git_test.dart b/pkg/front_end/test/read_dill_from_binary_md_git_test.dart
index ec6277b..30a5ebd 100644
--- a/pkg/front_end/test/read_dill_from_binary_md_git_test.dart
+++ b/pkg/front_end/test/read_dill_from_binary_md_git_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io' show File, Platform;
 
 import 'package:kernel/target/targets.dart' show NoneTarget, TargetFlags;
diff --git a/pkg/front_end/test/relativize_test.dart b/pkg/front_end/test/relativize_test.dart
index c42fac6..ce5e36b 100644
--- a/pkg/front_end/test/relativize_test.dart
+++ b/pkg/front_end/test/relativize_test.dart
@@ -2,14 +2,12 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:_fe_analyzer_shared/src/util/relativize.dart';
 import 'package:test/test.dart';
 
 void main() {
   test('test relativeUri', () {
-    void c(String expected, String base, String path, bool isWindows) {
+    void c(String expected, String base, String path, bool? isWindows) {
       if (isWindows == null) {
         c(expected, base, path, true);
         c(expected, base, path, false);
diff --git a/pkg/front_end/test/scanner_replacement_test.dart b/pkg/front_end/test/scanner_replacement_test.dart
index dff9d26..f936db9 100644
--- a/pkg/front_end/test/scanner_replacement_test.dart
+++ b/pkg/front_end/test/scanner_replacement_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:_fe_analyzer_shared/src/scanner/scanner.dart' as fasta;
 import 'package:_fe_analyzer_shared/src/scanner/error_token.dart' as fasta;
 import 'package:_fe_analyzer_shared/src/scanner/token.dart' as analyzer;
@@ -30,7 +28,7 @@
 class ScannerTest_Replacement extends ScannerTestBase {
   @override
   analyzer.Token scanWithListener(String source, ErrorListener listener,
-      {fasta.ScannerConfiguration configuration}) {
+      {fasta.ScannerConfiguration? configuration}) {
     // Process the source similar to
     // pkg/analyzer/lib/src/dart/scanner/scanner.dart
     // to simulate replacing the analyzer scanner
@@ -49,9 +47,9 @@
   }
 
   void _assertOpenClosePair(String source) {
-    analyzer.BeginToken open = _scan(source);
-    fasta.Token close = open.next;
-    expect(close.next.isEof, isTrue);
+    analyzer.BeginToken open = _scan(source) as analyzer.BeginToken;
+    fasta.Token close = open.next!;
+    expect(close.next!.isEof, isTrue);
     expect(open.endGroup, close);
     expect(open.isSynthetic, isFalse);
     expect(close.isSynthetic, isFalse);
@@ -59,9 +57,10 @@
 
   void _assertOpenOnly(String source, String expectedCloser) {
     ErrorListener listener = new ErrorListener();
-    analyzer.BeginToken open = scanWithListener(source, listener);
-    fasta.Token close = open.next;
-    expect(close.next.isEof, isTrue);
+    analyzer.BeginToken open =
+        scanWithListener(source, listener) as analyzer.BeginToken;
+    fasta.Token close = open.next!;
+    expect(close.next!.isEof, isTrue);
     expect(open.endGroup, close);
     expect(open.isSynthetic, isFalse);
     expect(close.isSynthetic, isTrue);
@@ -82,7 +81,7 @@
     // ... but the length does *not* include the additional character
     // so as to be true to the original source.
     expect(token.length, source.length);
-    expect(token.next.isEof, isTrue);
+    expect(token.next!.isEof, isTrue);
     expect(listener.errors, hasLength(1));
     TestError error = listener.errors[0];
     expect(error.errorCode, ScannerErrorCode.MISSING_DIGIT);
@@ -93,8 +92,8 @@
   void test_lt() {
     // fasta does not automatically insert a closer for '<'
     // because it could be part of an expression rather than an opener
-    analyzer.BeginToken lt = _scan('<');
-    expect(lt.next.isEof, isTrue);
+    analyzer.BeginToken lt = _scan('<') as analyzer.BeginToken;
+    expect(lt.next!.isEof, isTrue);
     expect(lt.isSynthetic, isFalse);
   }
 
@@ -138,16 +137,18 @@
     // where both ')' are synthetic
     ErrorListener listener = new ErrorListener();
     var stringStart = scanWithListener(r'"${({(}}"', listener);
-    analyzer.BeginToken interpolationStart = stringStart.next;
-    analyzer.BeginToken openParen1 = interpolationStart.next;
-    analyzer.BeginToken openBrace = openParen1.next;
-    analyzer.BeginToken openParen2 = openBrace.next;
-    var closeParen2 = openParen2.next;
-    var closeBrace = closeParen2.next;
-    var closeParen1 = closeBrace.next;
-    var interpolationEnd = closeParen1.next;
-    var stringEnd = interpolationEnd.next;
-    var eof = stringEnd.next;
+    analyzer.BeginToken interpolationStart =
+        stringStart.next as analyzer.BeginToken;
+    analyzer.BeginToken openParen1 =
+        interpolationStart.next as analyzer.BeginToken;
+    analyzer.BeginToken openBrace = openParen1.next as analyzer.BeginToken;
+    analyzer.BeginToken openParen2 = openBrace.next as analyzer.BeginToken;
+    var closeParen2 = openParen2.next!;
+    var closeBrace = closeParen2.next!;
+    var closeParen1 = closeBrace.next!;
+    var interpolationEnd = closeParen1.next!;
+    var stringEnd = interpolationEnd.next!;
+    var eof = stringEnd.next!;
 
     expect(interpolationStart.endToken, same(interpolationEnd));
     expect(interpolationEnd.isSynthetic, isFalse);
@@ -168,14 +169,15 @@
   void test_unmatched_openers() {
     ErrorListener listener = new ErrorListener();
     // fasta inserts missing closers except for '<'
-    analyzer.BeginToken openBrace = scanWithListener('{[(<', listener);
-    analyzer.BeginToken openBracket = openBrace.next;
-    analyzer.BeginToken openParen = openBracket.next;
-    analyzer.BeginToken openLT = openParen.next;
-    var closeParen = openLT.next;
-    var closeBracket = closeParen.next;
-    var closeBrace = closeBracket.next;
-    var eof = closeBrace.next;
+    analyzer.BeginToken openBrace =
+        scanWithListener('{[(<', listener) as analyzer.BeginToken;
+    analyzer.BeginToken openBracket = openBrace.next as analyzer.BeginToken;
+    analyzer.BeginToken openParen = openBracket.next as analyzer.BeginToken;
+    analyzer.BeginToken openLT = openParen.next as analyzer.BeginToken;
+    var closeParen = openLT.next!;
+    var closeBracket = closeParen.next!;
+    var closeBrace = closeBracket.next!;
+    var eof = closeBrace.next!;
 
     expect(openBrace.endGroup, same(closeBrace));
     expect(openBracket.endGroup, same(closeBracket));
@@ -201,13 +203,13 @@
     // The default recovery strategy used by scanString
     // places all error tokens at the head of the stream.
     while (token.type == analyzer.TokenType.BAD_INPUT) {
-      translateErrorToken(token,
-          (ScannerErrorCode errorCode, int offset, List<Object> arguments) {
+      translateErrorToken(token as fasta.ErrorToken,
+          (ScannerErrorCode errorCode, int offset, List<Object>? arguments) {
         listener.errors.add(new TestError(offset, errorCode, arguments));
       });
-      token = token.next;
+      token = token.next!;
     }
-    if (!token.previous.isEof) {
+    if (!token.previous!.isEof) {
       new analyzer.Token.eof(-1).setNext(token);
     }
     return token;
@@ -217,20 +219,20 @@
   void assertValidTokenStream(fasta.Token firstToken,
       {bool errorsFirst: false}) {
     fasta.Token token = firstToken;
-    fasta.Token previous = token.previous;
+    fasta.Token previous = token.previous!;
     expect(previous.isEof, isTrue, reason: 'Missing leading EOF');
     expect(previous.next, token, reason: 'Invalid leading EOF');
     expect(previous.previous, previous, reason: 'Invalid leading EOF');
     if (errorsFirst) {
       while (!token.isEof && token is fasta.ErrorToken) {
-        token = token.next;
+        token = token.next!;
       }
     }
     var isNotErrorToken = isNot(const TypeMatcher<fasta.ErrorToken>());
     while (!token.isEof) {
       if (errorsFirst) expect(token, isNotErrorToken);
       previous = token;
-      token = token.next;
+      token = token.next!;
       expect(token, isNotNull, reason: previous.toString());
       expect(token.previous, previous, reason: token.toString());
     }
@@ -258,7 +260,7 @@
       } else if (token is fasta.UnmatchedToken) {
         errorStack.add(token);
       }
-      token = token.next;
+      token = token.next!;
     }
     expect(openerStack, isEmpty, reason: 'Missing closers');
     expect(errorStack, isEmpty, reason: 'Extra error tokens');
diff --git a/pkg/front_end/test/scanner_test.dart b/pkg/front_end/test/scanner_test.dart
index cbf28bc..08c21e7 100644
--- a/pkg/front_end/test/scanner_test.dart
+++ b/pkg/front_end/test/scanner_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:_fe_analyzer_shared/src/base/errors.dart';
 import 'package:_fe_analyzer_shared/src/scanner/abstract_scanner.dart'
     show AbstractScanner, ScannerConfiguration;
@@ -82,7 +80,7 @@
 
 abstract class ScannerTestBase {
   Token scanWithListener(String source, ErrorListener listener,
-      {ScannerConfiguration configuration});
+      {ScannerConfiguration? configuration});
 
   void test_ampersand() {
     _assertToken(TokenType.AMPERSAND, "&");
@@ -104,9 +102,9 @@
 
   void test_angle_brackets() {
     var lessThan = _scan('<String>');
-    var identifier = lessThan.next;
-    var greaterThan = identifier.next;
-    expect(greaterThan.next.type, TokenType.EOF);
+    var identifier = lessThan.next!;
+    var greaterThan = identifier.next!;
+    expect(greaterThan.next!.type, TokenType.EOF);
     // Analyzer's token streams don't consider "<" to be an opener
     // but fasta does.
     if (lessThan is BeginToken) {
@@ -119,8 +117,8 @@
     Token token = _scan("async*");
     expect(token.type.isKeyword, true);
     expect(token.lexeme, 'async');
-    expect(token.next.type, TokenType.STAR);
-    expect(token.next.next.type, TokenType.EOF);
+    expect(token.next!.type, TokenType.STAR);
+    expect(token.next!.next!.type, TokenType.EOF);
   }
 
   void test_at() {
@@ -197,30 +195,30 @@
     Token token = _scan("/* x */ /* y */ z");
     expect(token.type, TokenType.IDENTIFIER);
     expect(token.precedingComments, isNotNull);
-    expect(token.precedingComments.value(), "/* x */");
-    expect(token.precedingComments.previous, isNull);
-    expect(token.precedingComments.next, isNotNull);
-    expect(token.precedingComments.next.value(), "/* y */");
+    expect(token.precedingComments!.value(), "/* x */");
+    expect(token.precedingComments!.previous, isNull);
+    expect(token.precedingComments!.next, isNotNull);
+    expect(token.precedingComments!.next!.value(), "/* y */");
     expect(
-        token.precedingComments.next.previous, same(token.precedingComments));
-    expect(token.precedingComments.next.next, isNull);
+        token.precedingComments!.next!.previous, same(token.precedingComments));
+    expect(token.precedingComments!.next!.next, isNull);
   }
 
   void test_comment_multi_consecutive_3() {
     Token token = _scan("/* x */ /* y */ /* z */ a");
     expect(token.type, TokenType.IDENTIFIER);
     expect(token.precedingComments, isNotNull);
-    expect(token.precedingComments.value(), "/* x */");
-    expect(token.precedingComments.previous, isNull);
-    expect(token.precedingComments.next, isNotNull);
-    expect(token.precedingComments.next.value(), "/* y */");
+    expect(token.precedingComments!.value(), "/* x */");
+    expect(token.precedingComments!.previous, isNull);
+    expect(token.precedingComments!.next, isNotNull);
+    expect(token.precedingComments!.next!.value(), "/* y */");
     expect(
-        token.precedingComments.next.previous, same(token.precedingComments));
-    expect(token.precedingComments.next.next, isNotNull);
-    expect(token.precedingComments.next.next.value(), "/* z */");
-    expect(token.precedingComments.next.next.previous,
-        same(token.precedingComments.next));
-    expect(token.precedingComments.next.next.next, isNull);
+        token.precedingComments!.next!.previous, same(token.precedingComments));
+    expect(token.precedingComments!.next!.next, isNotNull);
+    expect(token.precedingComments!.next!.next!.value(), "/* z */");
+    expect(token.precedingComments!.next!.next!.previous,
+        same(token.precedingComments!.next));
+    expect(token.precedingComments!.next!.next!.next, isNull);
   }
 
   void test_comment_multi_unterminated() {
@@ -307,9 +305,9 @@
   void test_hexadecimal_missingDigit() {
     var token = _assertError(ScannerErrorCode.MISSING_HEX_DIGIT, 5, "a = 0x");
     expect(token.lexeme, 'a');
-    token = token.next;
+    token = token.next!;
     expect(token.lexeme, '=');
-    token = token.next;
+    token = token.next!;
     expect(token.lexeme, '0x0');
   }
 
@@ -334,12 +332,12 @@
     ]);
     var token = tokens;
     expect(token.lexeme, 'a');
-    token = token.next;
+    token = token.next!;
     expect(token.lexeme, '=');
-    token = token.next;
+    token = token.next!;
     expect(token.type, TokenType.IDENTIFIER);
     expect(token.lexeme, "Shche\u0433lov\u0429x");
-    token = token.next;
+    token = token.next!;
     expect(token.lexeme, ';');
   }
 
@@ -355,12 +353,12 @@
     var token = _assertError(
         ScannerErrorCode.ILLEGAL_CHARACTER, 4, 'a = \u0429;', [0x429]);
     expect(token.lexeme, 'a');
-    token = token.next;
+    token = token.next!;
     expect(token.lexeme, '=');
-    token = token.next;
+    token = token.next!;
     expect(token.type, TokenType.IDENTIFIER);
     expect(token.lexeme, "\u0429");
-    token = token.next;
+    token = token.next!;
     expect(token.lexeme, ';');
   }
 
@@ -711,41 +709,41 @@
   }
 
   void test_matching_braces() {
-    BeginToken openBrace1 = _scan('{1: {2: 3}}');
-    var one = openBrace1.next;
-    var colon1 = one.next;
-    BeginToken openBrace2 = colon1.next;
-    var two = openBrace2.next;
-    var colon2 = two.next;
-    var three = colon2.next;
-    var closeBrace1 = three.next;
-    var closeBrace2 = closeBrace1.next;
-    expect(closeBrace2.next.type, TokenType.EOF);
+    BeginToken openBrace1 = _scan('{1: {2: 3}}') as BeginToken;
+    var one = openBrace1.next!;
+    var colon1 = one.next!;
+    BeginToken openBrace2 = colon1.next as BeginToken;
+    var two = openBrace2.next!;
+    var colon2 = two.next!;
+    var three = colon2.next!;
+    var closeBrace1 = three.next!;
+    var closeBrace2 = closeBrace1.next!;
+    expect(closeBrace2.next!.type, TokenType.EOF);
     expect(openBrace1.endToken, same(closeBrace2));
     expect(openBrace2.endToken, same(closeBrace1));
   }
 
   void test_matching_brackets() {
-    BeginToken openBracket1 = _scan('[1, [2]]');
-    var one = openBracket1.next;
-    var comma = one.next;
-    BeginToken openBracket2 = comma.next;
-    var two = openBracket2.next;
-    var closeBracket1 = two.next;
-    var closeBracket2 = closeBracket1.next;
-    expect(closeBracket2.next.type, TokenType.EOF);
+    BeginToken openBracket1 = _scan('[1, [2]]') as BeginToken;
+    var one = openBracket1.next!;
+    var comma = one.next!;
+    BeginToken openBracket2 = comma.next as BeginToken;
+    var two = openBracket2.next!;
+    var closeBracket1 = two.next!;
+    var closeBracket2 = closeBracket1.next!;
+    expect(closeBracket2.next!.type, TokenType.EOF);
     expect(openBracket1.endToken, same(closeBracket2));
     expect(openBracket2.endToken, same(closeBracket1));
   }
 
   void test_matching_parens() {
-    BeginToken openParen1 = _scan('(f(x))');
-    var f = openParen1.next;
-    BeginToken openParen2 = f.next;
-    var x = openParen2.next;
-    var closeParen1 = x.next;
-    var closeParen2 = closeParen1.next;
-    expect(closeParen2.next.type, TokenType.EOF);
+    BeginToken openParen1 = _scan('(f(x))') as BeginToken;
+    var f = openParen1.next!;
+    BeginToken openParen2 = f.next as BeginToken;
+    var x = openParen2.next!;
+    var closeParen1 = x.next!;
+    var closeParen2 = closeParen1.next!;
+    expect(closeParen2.next!.type, TokenType.EOF);
     expect(openParen1.endToken, same(closeParen2));
     expect(openParen2.endToken, same(closeParen1));
   }
@@ -769,10 +767,10 @@
     // In this particular case, fasta cannot find an opener for ']'
     // and thus marks ']' as an error and moves on.
     ErrorListener listener = new ErrorListener();
-    BeginToken openParen = scanWithListener('(])', listener);
-    var closeBracket = openParen.next;
-    var closeParen = closeBracket.next;
-    expect(closeParen.next.type, TokenType.EOF);
+    BeginToken openParen = scanWithListener('(])', listener) as BeginToken;
+    var closeBracket = openParen.next!;
+    var closeParen = closeBracket.next!;
+    expect(closeParen.next!.type, TokenType.EOF);
     expect(openParen.endToken, same(closeParen));
     listener.assertNoErrors();
   }
@@ -792,15 +790,15 @@
     //    2 recoveries).
     // Both options are "equally bad" and the first choise is made.
     ErrorListener listener = new ErrorListener();
-    BeginToken openBracket = scanWithListener('[(])', listener);
-    BeginToken openParen = openBracket.next;
-    var closeParen = openParen.next;
+    BeginToken openBracket = scanWithListener('[(])', listener) as BeginToken;
+    BeginToken openParen = openBracket.next as BeginToken;
+    var closeParen = openParen.next!;
     expect(closeParen.isSynthetic, isTrue);
-    var closeBracket = closeParen.next;
+    var closeBracket = closeParen.next!;
     expect(closeBracket.isSynthetic, isFalse);
-    var closeParen2 = closeBracket.next;
+    var closeParen2 = closeBracket.next!;
     expect(closeParen2.isSynthetic, isFalse);
-    expect(closeParen2.next.type, TokenType.EOF);
+    expect(closeParen2.next!.type, TokenType.EOF);
     expect(openBracket.endToken, same(closeBracket));
     expect(openParen.endToken, same(closeParen));
     listener.assertErrors([
@@ -813,17 +811,17 @@
     // closer to be mismatched, which means that `([)` parses as three unmatched
     // tokens.
     ErrorListener listener = new ErrorListener();
-    BeginToken openParen = scanWithListener('([)', listener);
-    BeginToken openBracket = openParen.next;
+    BeginToken openParen = scanWithListener('([)', listener) as BeginToken;
+    BeginToken openBracket = openParen.next as BeginToken;
     // When openers and closers are mismatched,
     // fasta favors considering the opener to be mismatched
     // and inserts synthetic closers as needed.
     // `([)` is scanned as `([])` where `]` is synthetic.
-    var closeBracket = openBracket.next;
+    var closeBracket = openBracket.next!;
     expect(closeBracket.isSynthetic, isTrue);
-    var closeParen = closeBracket.next;
+    var closeParen = closeBracket.next!;
     expect(closeParen.isSynthetic, isFalse);
-    expect(closeParen.next.type, TokenType.EOF);
+    expect(closeParen.next!.type, TokenType.EOF);
     expect(openBracket.endToken, closeBracket);
     expect(openParen.endToken, closeParen);
     listener.assertErrors([
@@ -837,14 +835,14 @@
     // unmatched tokens, which means that `"${({(}}"` parses as though the open
     // parens are unmatched but everything else is matched.
     var stringStart = _scan(r'"${({(}}"');
-    BeginToken interpolationStart = stringStart.next;
-    BeginToken openParen1 = interpolationStart.next;
-    BeginToken openBrace = openParen1.next;
-    BeginToken openParen2 = openBrace.next;
-    var closeBrace = openParen2.next;
-    var interpolationEnd = closeBrace.next;
-    var stringEnd = interpolationEnd.next;
-    expect(stringEnd.next.type, TokenType.EOF);
+    BeginToken interpolationStart = stringStart.next as BeginToken;
+    BeginToken openParen1 = interpolationStart.next as BeginToken;
+    BeginToken openBrace = openParen1.next as BeginToken;
+    BeginToken openParen2 = openBrace.next as BeginToken;
+    var closeBrace = openParen2.next!;
+    var interpolationEnd = closeBrace.next!;
+    var stringEnd = interpolationEnd.next!;
+    expect(stringEnd.next!.type, TokenType.EOF);
     expect(interpolationStart.endToken, same(interpolationEnd));
     expect(openParen1.endToken, isNull);
     expect(openBrace.endToken, same(closeBrace));
@@ -966,12 +964,12 @@
   void test_startAndEnd() {
     Token token = _scan("a");
     expect(token.offset, 0);
-    Token previous = token.previous;
+    Token previous = token.previous!;
     expect(previous.next, token);
     expect(previous.previous, previous);
     expect(previous.type, TokenType.EOF);
     expect(previous.offset, -1);
-    Token next = token.next;
+    Token next = token.next!;
     expect(next.next, next);
     expect(next.previous, token);
     expect(next.type, TokenType.EOF);
@@ -1290,8 +1288,8 @@
     Token token = _scan("sync*");
     expect(token.type.isKeyword, true);
     expect(token.lexeme, 'sync');
-    expect(token.next.type, TokenType.STAR);
-    expect(token.next.next.type, TokenType.EOF);
+    expect(token.next!.type, TokenType.STAR);
+    expect(token.next!.next!.type, TokenType.EOF);
   }
 
   void test_tilde() {
@@ -1312,10 +1310,10 @@
   }
 
   void test_unmatched_openers() {
-    BeginToken openBrace = _scan('{[(');
-    BeginToken openBracket = openBrace.next;
-    BeginToken openParen = openBracket.next;
-    expect(openParen.next.type, TokenType.EOF);
+    BeginToken openBrace = _scan('{[(') as BeginToken;
+    BeginToken openBracket = openBrace.next as BeginToken;
+    BeginToken openParen = openBracket.next as BeginToken;
+    expect(openParen.next!.type, TokenType.EOF);
     expect(openBrace.endToken, isNull);
     expect(openBracket.endToken, isNull);
     expect(openParen.endToken, isNull);
@@ -1328,9 +1326,9 @@
     Token token = _scan(source);
     expect(token, isNotNull);
     expect(token.type, TokenType.EOF);
-    Token comment = token.precedingComments;
+    Token? comment = token.precedingComments;
     expect(comment, isNotNull);
-    expect(comment.type, commentType);
+    expect(comment!.type, commentType);
     expect(comment.offset, 0);
     expect(comment.length, source.length);
     expect(comment.lexeme, source);
@@ -1342,7 +1340,7 @@
     expect(token.type, TokenType.EOF);
     comment = token.precedingComments;
     expect(comment, isNotNull);
-    expect(comment.type, commentType);
+    expect(comment!.type, commentType);
     expect(comment.offset, 0);
     expect(comment.length, source.length);
     expect(comment.lexeme, source);
@@ -1358,7 +1356,7 @@
    */
   Token _assertError(
       ScannerErrorCode expectedError, int expectedOffset, String source,
-      [List<Object> arguments]) {
+      [List<Object>? arguments]) {
     ErrorListener listener = new ErrorListener();
     var tokens = scanWithListener(source, listener);
     listener.assertErrors(
@@ -1388,7 +1386,7 @@
    * with the same lexeme as the original source.
    */
   void _assertKeywordToken(String source,
-      {ScannerConfiguration configuration}) {
+      {ScannerConfiguration? configuration}) {
     Token token = _scan(source, configuration: configuration);
     expect(token, isNotNull);
     expect(token.type.isKeyword, true);
@@ -1407,7 +1405,7 @@
     value = token.value();
     expect(value is Keyword, isTrue);
     expect((value as Keyword).lexeme, source);
-    expect(token.next.type, TokenType.EOF);
+    expect(token.next!.type, TokenType.EOF);
   }
 
   /**
@@ -1415,7 +1413,7 @@
    * token with the same lexeme as the original source.
    */
   void _assertNotKeywordToken(String source,
-      {ScannerConfiguration configuration}) {
+      {ScannerConfiguration? configuration}) {
     Token token = _scan(source, configuration: configuration);
     expect(token, isNotNull);
     expect(token.type.isKeyword, false);
@@ -1428,7 +1426,7 @@
     expect(token.offset, 1);
     expect(token.length, source.length);
     expect(token.lexeme, source);
-    expect(token.next.type, TokenType.EOF);
+    expect(token.next!.type, TokenType.EOF);
   }
 
   /**
@@ -1494,10 +1492,11 @@
 
   void _checkTokens(Token firstToken, List<Token> expectedTokens) {
     expect(firstToken, isNotNull);
-    Token token = firstToken;
+    Token? token = firstToken;
     for (int i = 0; i < expectedTokens.length; i++) {
       Token expectedToken = expectedTokens[i];
-      expect(token.type, expectedToken.type, reason: "Wrong type for token $i");
+      expect(token!.type, expectedToken.type,
+          reason: "Wrong type for token $i");
       expect(token.offset, expectedToken.offset,
           reason: "Wrong offset for token $i");
       expect(token.length, expectedToken.length,
@@ -1507,11 +1506,11 @@
       token = token.next;
       expect(token, isNotNull);
     }
-    expect(token.type, TokenType.EOF);
+    expect(token!.type, TokenType.EOF);
   }
 
   Token _scan(String source,
-      {ScannerConfiguration configuration, bool ignoreErrors: false}) {
+      {ScannerConfiguration? configuration, bool ignoreErrors: false}) {
     ErrorListener listener = new ErrorListener();
     Token token =
         scanWithListener(source, listener, configuration: configuration);
@@ -1539,7 +1538,7 @@
 class TestError {
   final int offset;
   final ErrorCode errorCode;
-  final List<Object> arguments;
+  final List<Object>? arguments;
 
   TestError(this.offset, this.errorCode, this.arguments);
 
@@ -1547,7 +1546,7 @@
   int get hashCode {
     int h = combineHash(combineHash(0, offset), errorCode.hashCode);
     if (arguments != null) {
-      for (Object argument in arguments) {
+      for (Object argument in arguments!) {
         h = combineHash(h, argument.hashCode);
       }
     }
@@ -1561,9 +1560,9 @@
         errorCode == other.errorCode) {
       if (arguments == null) return other.arguments == null;
       if (other.arguments == null) return false;
-      if (arguments.length != other.arguments.length) return false;
-      for (int i = 0; i < arguments.length; i++) {
-        if (arguments[i] != other.arguments[i]) return false;
+      if (arguments!.length != other.arguments!.length) return false;
+      for (int i = 0; i < arguments!.length; i++) {
+        if (arguments![i] != other.arguments![i]) return false;
       }
       return true;
     }
@@ -1572,7 +1571,7 @@
 
   @override
   String toString() {
-    var argString = arguments == null ? '' : '(${arguments.join(', ')})';
+    var argString = arguments == null ? '' : '(${arguments!.join(', ')})';
     return 'Error($offset, $errorCode$argString)';
   }
 }
diff --git a/pkg/front_end/test/scheme_based_file_system_test.dart b/pkg/front_end/test/scheme_based_file_system_test.dart
index 2cbe46e..3be6daf 100644
--- a/pkg/front_end/test/scheme_based_file_system_test.dart
+++ b/pkg/front_end/test/scheme_based_file_system_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:front_end/src/api_prototype/file_system.dart';
 import 'package:front_end/src/scheme_based_file_system.dart';
 
@@ -16,10 +14,10 @@
     var fileSystem =
         new SchemeBasedFileSystem({'scheme1': fs1, 'scheme2': fs2});
 
-    MockFileSystemEntity e1 =
-        fileSystem.entityForUri(Uri.parse('scheme1:a.dart'));
-    MockFileSystemEntity e2 =
-        fileSystem.entityForUri(Uri.parse('scheme2:a.dart'));
+    MockFileSystemEntity e1 = fileSystem
+        .entityForUri(Uri.parse('scheme1:a.dart')) as MockFileSystemEntity;
+    MockFileSystemEntity e2 = fileSystem
+        .entityForUri(Uri.parse('scheme2:a.dart')) as MockFileSystemEntity;
     expect(e1.fileSystem, fs1);
     expect(e2.fileSystem, fs2);
   });
diff --git a/pkg/front_end/test/severity_index_test.dart b/pkg/front_end/test/severity_index_test.dart
index 105e46b..e404f6a 100644
--- a/pkg/front_end/test/severity_index_test.dart
+++ b/pkg/front_end/test/severity_index_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity;
 
 /// Test that Severity has the expected indexes. Note that this is important
diff --git a/pkg/front_end/test/spell_checking_cleanup_lists.dart b/pkg/front_end/test/spell_checking_cleanup_lists.dart
index ea44732..f2b9684 100644
--- a/pkg/front_end/test/spell_checking_cleanup_lists.dart
+++ b/pkg/front_end/test/spell_checking_cleanup_lists.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io';
 
 import 'spell_checking_utils.dart' as spell;
@@ -12,7 +10,7 @@
   {
     spell.ensureDictionariesLoaded([spell.Dictionaries.common]);
     Set<String> commonWords =
-        spell.loadedDictionaries[spell.Dictionaries.common];
+        spell.loadedDictionaries![spell.Dictionaries.common]!;
     for (spell.Dictionaries dictionary in spell.Dictionaries.values) {
       if (dictionary == spell.Dictionaries.common) continue;
       Uri uri = spell.dictionaryToUri(dictionary);
@@ -30,7 +28,7 @@
   {
     spell.ensureDictionariesLoaded([spell.Dictionaries.cfeCode]);
     Set<String> codeWords =
-        spell.loadedDictionaries[spell.Dictionaries.cfeCode];
+        spell.loadedDictionaries![spell.Dictionaries.cfeCode]!;
     Uri uri = spell.dictionaryToUri(spell.Dictionaries.cfeTests);
     List<String> keep = <String>[];
     for (String line in new File.fromUri(uri).readAsLinesSync()) {
diff --git a/pkg/front_end/test/spell_checking_utils.dart b/pkg/front_end/test/spell_checking_utils.dart
index c08fb4c..c560644 100644
--- a/pkg/front_end/test/spell_checking_utils.dart
+++ b/pkg/front_end/test/spell_checking_utils.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io' show File, stdin, stdout;
 
 import "utils/io_utils.dart";
@@ -19,17 +17,17 @@
   denylist,
 }
 
-Map<Dictionaries, Set<String>> loadedDictionaries;
+Map<Dictionaries, Set<String>>? loadedDictionaries;
 
 SpellingResult spellcheckString(String s,
-    {List<Dictionaries> dictionaries, bool splitAsCode: false}) {
+    {List<Dictionaries>? dictionaries, bool splitAsCode: false}) {
   dictionaries ??= const [Dictionaries.common];
   ensureDictionariesLoaded(dictionaries);
 
-  List<String> wrongWords;
-  List<List<String>> wrongWordsAlternatives;
-  List<int> wrongWordsOffset;
-  List<bool> wrongWordDenylisted;
+  List<String>? wrongWords;
+  List<List<String>?>? wrongWordsAlternatives;
+  List<int>? wrongWordsOffset;
+  List<bool>? wrongWordDenylisted;
   List<int> wordOffsets = <int>[];
   List<String> words =
       splitStringIntoWords(s, wordOffsets, splitAsCode: splitAsCode);
@@ -37,7 +35,7 @@
   for (int j = 0; j < dictionaries.length; j++) {
     Dictionaries dictionaryType = dictionaries[j];
     if (dictionaryType == Dictionaries.denylist) continue;
-    Set<String> dictionary = loadedDictionaries[dictionaryType];
+    Set<String> dictionary = loadedDictionaries![dictionaryType]!;
     dictionariesUnpacked.add(dictionary);
   }
   for (int i = 0; i < words.length; i++) {
@@ -61,7 +59,7 @@
       wrongWordsOffset.add(offset);
       wrongWordDenylisted ??= <bool>[];
       wrongWordDenylisted
-          .add(loadedDictionaries[Dictionaries.denylist].contains(word));
+          .add(loadedDictionaries![Dictionaries.denylist]!.contains(word));
     }
   }
 
@@ -69,8 +67,8 @@
       wrongWordsAlternatives);
 }
 
-List<String> findAlternatives(String word, List<Set<String>> dictionaries) {
-  List<String> result;
+List<String>? findAlternatives(String word, List<Set<String>> dictionaries) {
+  List<String>? result;
 
   bool check(String w) {
     for (int j = 0; j < dictionaries.length; j++) {
@@ -81,8 +79,7 @@
   }
 
   void ok(String w) {
-    result ??= <String>[];
-    result.add(w);
+    (result ??= <String>[]).add(w);
   }
 
   // Delete a letter, insert a letter or change a letter and lookup.
@@ -111,10 +108,10 @@
 }
 
 class SpellingResult {
-  final List<String> misspelledWords;
-  final List<int> misspelledWordsOffset;
-  final List<bool> misspelledWordsDenylisted;
-  final List<List<String>> misspelledWordsAlternatives;
+  final List<String>? misspelledWords;
+  final List<int>? misspelledWordsOffset;
+  final List<bool>? misspelledWordsDenylisted;
+  final List<List<String>?>? misspelledWordsAlternatives;
 
   SpellingResult(this.misspelledWords, this.misspelledWordsOffset,
       this.misspelledWordsDenylisted, this.misspelledWordsAlternatives);
@@ -140,19 +137,19 @@
 
   loadedDictionaries ??= new Map<Dictionaries, Set<String>>();
   // Ensure the denylist is loaded.
-  Set<String> denylistDictionary = loadedDictionaries[Dictionaries.denylist];
+  Set<String>? denylistDictionary = loadedDictionaries![Dictionaries.denylist];
   if (denylistDictionary == null) {
     denylistDictionary = new Set<String>();
-    loadedDictionaries[Dictionaries.denylist] = denylistDictionary;
+    loadedDictionaries![Dictionaries.denylist] = denylistDictionary;
     addWords(dictionaryToUri(Dictionaries.denylist), denylistDictionary);
   }
 
   for (int j = 0; j < dictionaries.length; j++) {
     Dictionaries dictionaryType = dictionaries[j];
-    Set<String> dictionary = loadedDictionaries[dictionaryType];
+    Set<String>? dictionary = loadedDictionaries![dictionaryType];
     if (dictionary == null) {
       dictionary = new Set<String>();
-      loadedDictionaries[dictionaryType] = dictionary;
+      loadedDictionaries![dictionaryType] = dictionary;
       addWords(dictionaryToUri(dictionaryType), dictionary);
       // Check that no good words occur in the denylist.
       for (String s in dictionary) {
@@ -182,7 +179,6 @@
       return repoDir
           .resolve("pkg/front_end/test/spell_checking_list_denylist.txt");
   }
-  throw "Unknown Dictionary";
 }
 
 List<String> splitStringIntoWords(String s, List<int> splitOffsets,
@@ -370,7 +366,7 @@
     print("The following word(s) were reported as unknown:");
     print("----------------");
 
-    Dictionaries dictionaryToUse;
+    Dictionaries? dictionaryToUse;
     if (dictionaries.contains(Dictionaries.cfeTests)) {
       dictionaryToUse = Dictionaries.cfeTests;
     } else if (dictionaries.contains(Dictionaries.cfeMessages)) {
@@ -391,11 +387,11 @@
       for (String s in reportedWords) {
         print("- $s");
         String answer;
-        bool add;
+        bool? add;
         while (true) {
           stdout.write("Do you want to add the word to the dictionary "
               "$dictionaryToUse (y/n)? ");
-          answer = stdin.readLineSync().trim().toLowerCase();
+          answer = stdin.readLineSync()!.trim().toLowerCase();
           switch (answer) {
             case "y":
             case "yes":
diff --git a/pkg/front_end/test/spell_checking_utils_test.dart b/pkg/front_end/test/spell_checking_utils_test.dart
index 07c8904..b91ed891 100644
--- a/pkg/front_end/test/spell_checking_utils_test.dart
+++ b/pkg/front_end/test/spell_checking_utils_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'spell_checking_utils.dart';
 
 void main() {
@@ -98,7 +96,7 @@
   compareLists(actualOffsets, expectedOffsets);
 }
 
-void compareLists(List<dynamic> actual, List<dynamic> expected) {
+void compareLists(List<dynamic>? actual, List<dynamic>? expected) {
   if (actual == null && expected == null) return;
   if (actual == null) throw "Got null, expected $expected";
   if (expected == null) throw "Expected null, got $actual";
@@ -113,7 +111,7 @@
 }
 
 void expectAlternative(
-    String word, List<String> expected, Set<String> dictionary) {
-  List<String> alternatives = findAlternatives(word, [dictionary]);
+    String word, List<String>? expected, Set<String> dictionary) {
+  List<String>? alternatives = findAlternatives(word, [dictionary]);
   compareLists(alternatives, expected);
 }
diff --git a/pkg/front_end/test/spelling_test_base.dart b/pkg/front_end/test/spelling_test_base.dart
index fd1c94a..e6d43dc 100644
--- a/pkg/front_end/test/spelling_test_base.dart
+++ b/pkg/front_end/test/spelling_test_base.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io' show File, Platform;
 
 import 'dart:typed_data' show Uint8List;
@@ -37,7 +35,7 @@
   final bool interactive;
   final bool onlyInGit;
 
-  SpellContext({this.interactive, this.onlyInGit});
+  SpellContext({required this.interactive, required this.onlyInGit});
 
   // Override special handling of negative tests.
   @override
@@ -75,7 +73,7 @@
         dictionaries,
         interactive,
         '"$dartPath" "$suitePath" -DonlyInGit=$onlyInGit -Dinteractive=true');
-    return null;
+    return new Future.value();
   }
 }
 
@@ -97,14 +95,13 @@
     Utf8BytesScanner scanner =
         new Utf8BytesScanner(bytes, includeComments: true);
     Token firstToken = scanner.tokenize();
-    if (firstToken == null) return null;
-    Token token = firstToken;
+    Token? token = firstToken;
 
-    List<String> errors;
+    List<String>? errors;
     Source source = new Source(
         scanner.lineStarts, rawBytes, description.uri, description.uri);
     void addErrorMessage(
-        int offset, String word, bool denylisted, List<String> alternatives) {
+        int offset, String word, bool denylisted, List<String>? alternatives) {
       errors ??= <String>[];
       String message;
       if (denylisted) {
@@ -128,7 +125,7 @@
             "- $dictionaryPathString\n";
       }
       Location location = source.getLocation(description.uri, offset);
-      errors.add(command_line_reporting.formatErrorMessage(
+      errors!.add(command_line_reporting.formatErrorMessage(
           source.getTextLine(location.line),
           location,
           word.length,
@@ -142,20 +139,20 @@
         return pass(description);
       }
       if (token.precedingComments != null) {
-        Token comment = token.precedingComments;
+        Token? comment = token.precedingComments;
         while (comment != null) {
           spell.SpellingResult spellingResult = spell.spellcheckString(
               comment.lexeme,
               splitAsCode: true,
               dictionaries: context.dictionaries);
           if (spellingResult.misspelledWords != null) {
-            for (int i = 0; i < spellingResult.misspelledWords.length; i++) {
-              bool denylisted = spellingResult.misspelledWordsDenylisted[i];
+            for (int i = 0; i < spellingResult.misspelledWords!.length; i++) {
+              bool denylisted = spellingResult.misspelledWordsDenylisted![i];
               if (context.onlyDenylisted && !denylisted) continue;
               int offset =
-                  comment.offset + spellingResult.misspelledWordsOffset[i];
-              addErrorMessage(offset, spellingResult.misspelledWords[i],
-                  denylisted, spellingResult.misspelledWordsAlternatives[i]);
+                  comment.offset + spellingResult.misspelledWordsOffset![i];
+              addErrorMessage(offset, spellingResult.misspelledWords![i],
+                  denylisted, spellingResult.misspelledWordsAlternatives![i]);
             }
           }
           comment = comment.next;
@@ -167,12 +164,13 @@
             splitAsCode: true,
             dictionaries: context.dictionaries);
         if (spellingResult.misspelledWords != null) {
-          for (int i = 0; i < spellingResult.misspelledWords.length; i++) {
-            bool denylisted = spellingResult.misspelledWordsDenylisted[i];
+          for (int i = 0; i < spellingResult.misspelledWords!.length; i++) {
+            bool denylisted = spellingResult.misspelledWordsDenylisted![i];
             if (context.onlyDenylisted && !denylisted) continue;
-            int offset = token.offset + spellingResult.misspelledWordsOffset[i];
-            addErrorMessage(offset, spellingResult.misspelledWords[i],
-                denylisted, spellingResult.misspelledWordsAlternatives[i]);
+            int offset =
+                token.offset + spellingResult.misspelledWordsOffset![i];
+            addErrorMessage(offset, spellingResult.misspelledWords![i],
+                denylisted, spellingResult.misspelledWordsAlternatives![i]);
           }
         }
       } else if (token is KeywordToken || token is BeginToken) {
@@ -191,7 +189,7 @@
     if (errors == null) {
       return pass(description);
     } else {
-      return fail(description, errors.join("\n\n"));
+      return fail(description, errors!.join("\n\n"));
     }
   }
 }
diff --git a/pkg/front_end/test/spelling_test_external_targets.dart b/pkg/front_end/test/spelling_test_external_targets.dart
index 952f4b3..c2eef74 100644
--- a/pkg/front_end/test/spelling_test_external_targets.dart
+++ b/pkg/front_end/test/spelling_test_external_targets.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io' show Directory, File, FileSystemEntity;
 
 import 'package:testing/testing.dart'
@@ -30,7 +28,7 @@
 }
 
 class SpellContextExternal extends SpellContext {
-  SpellContextExternal({bool interactive, bool onlyInGit})
+  SpellContextExternal({required bool interactive, required bool onlyInGit})
       : super(interactive: interactive, onlyInGit: onlyInGit);
 
   @override
diff --git a/pkg/front_end/test/spelling_test_not_src_suite.dart b/pkg/front_end/test/spelling_test_not_src_suite.dart
index 9eb9921e..285e8fc 100644
--- a/pkg/front_end/test/spelling_test_not_src_suite.dart
+++ b/pkg/front_end/test/spelling_test_not_src_suite.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:testing/testing.dart' show Chain, runMe;
 
 import 'spelling_test_base.dart';
@@ -26,7 +24,7 @@
 }
 
 class SpellContextTest extends SpellContext {
-  SpellContextTest({bool interactive, bool onlyInGit})
+  SpellContextTest({required bool interactive, required bool onlyInGit})
       : super(interactive: interactive, onlyInGit: onlyInGit);
 
   @override
diff --git a/pkg/front_end/test/spelling_test_src_suite.dart b/pkg/front_end/test/spelling_test_src_suite.dart
index f77e918..066475a 100644
--- a/pkg/front_end/test/spelling_test_src_suite.dart
+++ b/pkg/front_end/test/spelling_test_src_suite.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:testing/testing.dart' show Chain, runMe;
 
 import 'spelling_test_base.dart';
@@ -26,7 +24,7 @@
 }
 
 class SpellContextSource extends SpellContext {
-  SpellContextSource({bool interactive, bool onlyInGit})
+  SpellContextSource({required bool interactive, required bool onlyInGit})
       : super(interactive: interactive, onlyInGit: onlyInGit);
 
   @override
diff --git a/pkg/front_end/test/split_dill_test.dart b/pkg/front_end/test/split_dill_test.dart
index 16484f2..9ff415d 100644
--- a/pkg/front_end/test/split_dill_test.dart
+++ b/pkg/front_end/test/split_dill_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io' show Directory, File, Platform, Process, ProcessResult;
 
 import 'dart:typed_data' show Uint8List;
diff --git a/pkg/front_end/test/standard_file_system_test.dart b/pkg/front_end/test/standard_file_system_test.dart
index c22913c..5092342 100644
--- a/pkg/front_end/test/standard_file_system_test.dart
+++ b/pkg/front_end/test/standard_file_system_test.dart
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 // SharedOptions=--supermixin
 
-// @dart = 2.9
-
 library front_end.test.standard_file_system_test;
 
 import 'dart:convert';
@@ -31,8 +29,8 @@
 
 @reflectiveTest
 class DirectoryTest extends _BaseTest {
-  String path;
-  FileSystemEntity dir;
+  late String path;
+  late FileSystemEntity dir;
 
   @override
   void setUp() {
@@ -70,8 +68,8 @@
 
 @reflectiveTest
 class FileTest extends _BaseTest {
-  String path;
-  FileSystemEntity file;
+  late String path;
+  late FileSystemEntity file;
 
   @override
   void setUp() {
@@ -146,7 +144,7 @@
 
 @reflectiveTest
 class StandardFileSystemTest extends _BaseTest {
-  Uri tempUri;
+  late Uri tempUri;
 
   @override
   void setUp() {
@@ -231,8 +229,8 @@
 }
 
 class _BaseTest {
-  io.Directory tempDirectory;
-  String tempPath;
+  late io.Directory tempDirectory;
+  late String tempPath;
 
   FileSystemEntity entityForPath(String path) =>
       StandardFileSystem.instance.entityForUri(p.toUri(path));
diff --git a/pkg/front_end/test/summary_generator_test.dart b/pkg/front_end/test/summary_generator_test.dart
index f3178d6..636cc41 100644
--- a/pkg/front_end/test/summary_generator_test.dart
+++ b/pkg/front_end/test/summary_generator_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:front_end/src/api_prototype/front_end.dart';
 import 'package:front_end/src/testing/compiler_common.dart';
 import 'package:kernel/kernel.dart';
@@ -13,19 +11,19 @@
 void main() {
   test('summary has no source-info by default', () async {
     var summary = await summarize(['a.dart'], allSources);
-    var component = loadComponentFromBytes(summary);
+    var component = loadComponentFromBytes(summary!);
 
     // Note: the kernel representation always includes the Uri entries, but
     // doesn't include the actual source here.
     for (Source source in component.uriToSource.values) {
       expect(source.source.length, 0);
-      expect(source.lineStarts.length, 0);
+      expect(source.lineStarts!.length, 0);
     }
   });
 
   test('summary includes declarations, but no method bodies', () async {
     var summary = await summarize(['a.dart'], allSources);
-    var component = loadComponentFromBytes(summary);
+    var component = loadComponentFromBytes(summary!);
     var aLib = findLibrary(component, 'a.dart');
     expect(aLib.importUri.path, '/a/b/c/a.dart');
     var classA = aLib.classes.first;
@@ -51,7 +49,7 @@
     var summaryD = await summarize(['d.dart'], sourcesWithABC,
         additionalDills: ['a.dill', 'bc.dill']);
 
-    checkDSummary(summaryD);
+    checkDSummary(summaryD!);
   });
 
   test('dependencies can be combined in any order', () async {
@@ -71,13 +69,13 @@
     // dill files and because of how the kernel loader merges definitions.
     var summaryD = await summarize(['d.dart'], sourcesWithABC,
         additionalDills: ['bc.dill', 'a.dill']);
-    checkDSummary(summaryD);
+    checkDSummary(summaryD!);
   });
 
   test('dependencies not included in truncated summaries', () async {
     // Note: by default this test is loading the SDK from summaries.
     var summaryA = await summarize(['a.dart'], allSources, truncate: true);
-    var component = loadComponentFromBytes(summaryA);
+    var component = loadComponentFromBytes(summaryA!);
     expect(component.libraries.length, 1);
     expect(
         component.libraries.single.importUri.path.endsWith('a.dart'), isTrue);
@@ -86,7 +84,7 @@
     sourcesWithA['a.dill'] = summaryA;
     var summaryB = await summarize(['b.dart'], sourcesWithA,
         additionalDills: ['a.dill'], truncate: true);
-    component = loadComponentFromBytes(summaryB);
+    component = loadComponentFromBytes(summaryB!);
     expect(component.libraries.length, 1);
     expect(
         component.libraries.single.importUri.path.endsWith('b.dart'), isTrue);
@@ -126,7 +124,7 @@
   expect(bClass.superclass, same(aClass));
 
   var dClass = dLib.classes.firstWhere((c) => c.name == 'D');
-  expect(dClass.superclass.superclass, same(bClass));
+  expect(dClass.superclass!.superclass, same(bClass));
 
   var dInterface = dClass.implementedTypes.first.classNode;
   expect(dInterface, same(aClass));
diff --git a/pkg/front_end/test/test_generator_test.dart b/pkg/front_end/test/test_generator_test.dart
index 2f009ea..73cd4e9 100644
--- a/pkg/front_end/test/test_generator_test.dart
+++ b/pkg/front_end/test/test_generator_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io' show exitCode, File, stdout;
 
 import 'package:front_end/src/api_prototype/compiler_options.dart';
diff --git a/pkg/front_end/test/textual_outline_test.dart b/pkg/front_end/test/textual_outline_test.dart
index 4a2af05..fd50c3f 100644
--- a/pkg/front_end/test/textual_outline_test.dart
+++ b/pkg/front_end/test/textual_outline_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE.md file.
 
-// @dart = 2.9
-
 import "dart:convert";
 
 import 'package:_fe_analyzer_shared/src/scanner/abstract_scanner.dart'
@@ -17,7 +15,7 @@
 
 void main() {
   // Doesn't sort if not asked to perform modelling.
-  String result = textualOutline(utf8.encode("""
+  String? result = textualOutline(utf8.encode("""
 b() { print("hello"); }
 a() { print("hello"); }
 """), scannerConfiguration, throwOnUnexpected: true, performModelling: false);
diff --git a/pkg/front_end/test/token_test.dart b/pkg/front_end/test/token_test.dart
index 9cf8377..301e6ec 100644
--- a/pkg/front_end/test/token_test.dart
+++ b/pkg/front_end/test/token_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
     show ScannerConfiguration, scanString;
 import 'package:_fe_analyzer_shared/src/scanner/token.dart';
@@ -38,11 +36,11 @@
 
     Token nextComment() {
       while (!token.isEof) {
-        Token comment = token.precedingComments;
-        token = token.next;
+        Token? comment = token.precedingComments;
+        token = token.next!;
         if (comment != null) return comment;
       }
-      return null;
+      throw new StateError("No comment found.");
     }
 
     Token comment = nextComment();
@@ -69,10 +67,10 @@
   void test_isSynthetic() {
     var token = scanString('/* 1 */ foo', includeComments: true).tokens;
     expect(token.isSynthetic, false);
-    expect(token.precedingComments.isSynthetic, false);
-    expect(token.previous.isSynthetic, true);
-    expect(token.next.isEof, true);
-    expect(token.next.isSynthetic, true);
+    expect(token.precedingComments!.isSynthetic, false);
+    expect(token.previous!.isSynthetic, true);
+    expect(token.next!.isEof, true);
+    expect(token.next!.isSynthetic, true);
   }
 
   void test_matchesAny() {
@@ -197,11 +195,11 @@
     expect(token.lexeme, 'true');
     expect(token.value(), Keyword.TRUE);
     // General tokens
-    token = token.next;
+    token = token.next!;
     expect(token.lexeme, '&');
     expect(token.value(), '&');
     // String tokens
-    token = token.next;
+    token = token.next!;
     expect(token.lexeme, '"home"');
     expect(token.value(), '"home"');
   }
diff --git a/pkg/front_end/test/tool/reload.dart b/pkg/front_end/test/tool/reload.dart
index 40a94a5..887ed3b 100644
--- a/pkg/front_end/test/tool/reload.dart
+++ b/pkg/front_end/test/tool/reload.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 /// A helper library to connect to an existing VM and trigger a hot-reload via
 /// its service protocol.
 ///
@@ -32,12 +30,12 @@
   /// An peer point used to send service protocol messages. The service
   /// protocol uses JSON rpc on top of web-sockets.
   json_rpc.Peer get rpc => _rpc ??= _createPeer();
-  json_rpc.Peer _rpc;
+  json_rpc.Peer? _rpc;
 
   /// The main isolate ID of the running VM. Needed to indicate to the VM which
   /// isolate to reload.
   FutureOr<String> get mainId async => _mainId ??= await _computeMainId();
-  String _mainId;
+  String? _mainId;
 
   RemoteVm([this.port = 8181]);
 
@@ -91,8 +89,8 @@
   Future disconnect() async {
     if (_rpc == null) return null;
     this._mainId = null;
-    if (!_rpc.isClosed) {
-      var future = _rpc.close();
+    if (!_rpc!.isClosed) {
+      var future = _rpc!.close();
       _rpc = null;
       return future;
     }
diff --git a/pkg/front_end/test/type_labeler_test.dart b/pkg/front_end/test/type_labeler_test.dart
index ec09139..af83e29 100644
--- a/pkg/front_end/test/type_labeler_test.dart
+++ b/pkg/front_end/test/type_labeler_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:kernel/ast.dart';
 
 import 'package:front_end/src/fasta/kernel/type_labeler.dart';
@@ -24,7 +22,7 @@
       }
     });
     expectations.forEach((Node node, String expected) {
-      Expect.stringEquals(expected, conversions[node].join());
+      Expect.stringEquals(expected, conversions[node]!.join());
     });
     int newlines = "\n".allMatches(labeler.originMessages).length;
     Expect.equals(bulletCount, newlines);
diff --git a/pkg/front_end/test/vm_service_coverage.dart b/pkg/front_end/test/vm_service_coverage.dart
index 0d4768a..2112bd0 100644
--- a/pkg/front_end/test/vm_service_coverage.dart
+++ b/pkg/front_end/test/vm_service_coverage.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE.md file.
 
-// @dart = 2.9
-
 import 'dart:async';
 
 import 'vm_service_helper.dart' as vmService;
@@ -31,12 +29,12 @@
   @override
   Future<void> run() async {
     vmService.VM vm = await serviceClient.getVM();
-    if (vm.isolates.length != 1) {
-      throw "Expected 1 isolate, got ${vm.isolates.length}";
+    if (vm.isolates!.length != 1) {
+      throw "Expected 1 isolate, got ${vm.isolates!.length}";
     }
-    vmService.IsolateRef isolateRef = vm.isolates.single;
-    await waitUntilIsolateIsRunnable(isolateRef.id);
-    await serviceClient.resume(isolateRef.id);
+    vmService.IsolateRef isolateRef = vm.isolates!.single;
+    await waitUntilIsolateIsRunnable(isolateRef.id!);
+    await serviceClient.resume(isolateRef.id!);
     Completer<String> cTimeout = new Completer();
     Timer timer = new Timer(new Duration(minutes: 20), () {
       cTimeout.complete("Timeout");
@@ -45,13 +43,13 @@
 
     Completer<String> cRunDone = new Completer();
     // ignore: unawaited_futures
-    waitUntilPaused(isolateRef.id).then((value) => cRunDone.complete("Done"));
+    waitUntilPaused(isolateRef.id!).then((value) => cRunDone.complete("Done"));
 
     await Future.any([cRunDone.future, cTimeout.future, cProcessExited.future]);
 
     timer.cancel();
 
-    if (!await isPausedAtExit(isolateRef.id)) {
+    if (!await isPausedAtExit(isolateRef.id!)) {
       killProcess();
       throw "Expected to be paused at exit, but is just paused!";
     }
@@ -59,36 +57,36 @@
     // Get and process coverage information.
     Stopwatch stopwatch = new Stopwatch()..start();
     vmService.SourceReport sourceReport = await serviceClient.getSourceReport(
-        isolateRef.id, [vmService.SourceReportKind.kCoverage],
+        isolateRef.id!, [vmService.SourceReportKind.kCoverage],
         forceCompile: forceCompilation);
     print("Got source report from VM in ${stopwatch.elapsedMilliseconds} ms");
     stopwatch.reset();
     Map<Uri, Coverage> coverages = {};
-    for (vmService.SourceReportRange range in sourceReport.ranges) {
-      vmService.ScriptRef script = sourceReport.scripts[range.scriptIndex];
-      Uri scriptUri = Uri.parse(script.uri);
+    for (vmService.SourceReportRange range in sourceReport.ranges!) {
+      vmService.ScriptRef script = sourceReport.scripts![range.scriptIndex!];
+      Uri scriptUri = Uri.parse(script.uri!);
       if (!includeCoverageFor(scriptUri)) continue;
       Coverage coverage = coverages[scriptUri] ??= new Coverage();
 
-      vmService.SourceReportCoverage sourceReportCoverage = range.coverage;
+      vmService.SourceReportCoverage? sourceReportCoverage = range.coverage;
       if (sourceReportCoverage == null) {
         // Range not compiled. Record the range if provided.
-        assert(!range.compiled);
-        if (range.startPos >= 0 || range.endPos >= 0) {
+        assert(!range.compiled!);
+        if (range.startPos! >= 0 || range.endPos! >= 0) {
           coverage.notCompiled
-              .add(new StartEndPair(range.startPos, range.endPos));
+              .add(new StartEndPair(range.startPos!, range.endPos!));
         }
         continue;
       }
-      coverage.hits.addAll(sourceReportCoverage.hits);
-      coverage.misses.addAll(sourceReportCoverage.misses);
+      coverage.hits.addAll(sourceReportCoverage.hits!);
+      coverage.misses.addAll(sourceReportCoverage.misses!);
     }
     print("Processed source report from VM in "
         "${stopwatch.elapsedMilliseconds} ms");
     stopwatch.reset();
 
     // It's paused at exit, so resuming should allow us to exit.
-    await serviceClient.resume(isolateRef.id);
+    await serviceClient.resume(isolateRef.id!);
 
     for (MapEntry<Uri, Coverage> entry in coverages.entries) {
       assert(entry.value.hits.intersection(entry.value.misses).isEmpty);
@@ -142,7 +140,7 @@
   String toString() => "[$startPos - $endPos]";
 
   @override
-  int compareTo(Object other) {
+  int compareTo(dynamic other) {
     if (other is! StartEndPair) return -1;
     StartEndPair o = other;
     return startPos - o.startPos;
diff --git a/pkg/front_end/test/vm_service_coverage_constant_evaluator.dart b/pkg/front_end/test/vm_service_coverage_constant_evaluator.dart
index 56b167b..3128758 100644
--- a/pkg/front_end/test/vm_service_coverage_constant_evaluator.dart
+++ b/pkg/front_end/test/vm_service_coverage_constant_evaluator.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE.md file.
 
-// @dart = 2.9
-
 import 'vm_service_coverage.dart' as helper;
 
 Future<void> main(List<String> args) async {
diff --git a/pkg/front_end/test/vm_service_for_leak_detection.dart b/pkg/front_end/test/vm_service_for_leak_detection.dart
index e5782a8..44b38c4 100644
--- a/pkg/front_end/test/vm_service_for_leak_detection.dart
+++ b/pkg/front_end/test/vm_service_for_leak_detection.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io';
 
 import "vm_service_heap_helper.dart" as helper;
diff --git a/pkg/front_end/test/vm_service_heap_finder.dart b/pkg/front_end/test/vm_service_heap_finder.dart
index a098317..a5aa11f 100644
--- a/pkg/front_end/test/vm_service_heap_finder.dart
+++ b/pkg/front_end/test/vm_service_heap_finder.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import "dart:io";
 
 import "vm_service_helper.dart" as vmService;
@@ -16,9 +14,9 @@
 }
 
 Future<void> main(List<String> args) async {
-  String connectTo;
-  String classToFind;
-  String whatToDo;
+  String? connectTo;
+  String? classToFind;
+  String? whatToDo;
   for (String arg in args) {
     if (arg.startsWith("--url=")) {
       connectTo = arg.substring("--url=".length);
@@ -62,25 +60,26 @@
 
 String ask(String question) {
   stdout.write("$question: ");
-  return stdin.readLineSync();
+  return stdin.readLineSync()!;
 }
 
 class VMServiceHeapHelperPrinter extends vmService.VMServiceHelper {
-  Future<void> printAllocationProfile(String isolateId, {String filter}) async {
+  Future<void> printAllocationProfile(String isolateId,
+      {String? filter}) async {
     await waitUntilIsolateIsRunnable(isolateId);
     vmService.AllocationProfile allocationProfile =
         await serviceClient.getAllocationProfile(isolateId);
-    for (vmService.ClassHeapStats member in allocationProfile.members) {
+    for (vmService.ClassHeapStats member in allocationProfile.members!) {
       if (filter != null) {
-        if (member.classRef.name != filter) continue;
+        if (member.classRef!.name != filter) continue;
       } else {
-        if (member.classRef.name == "") continue;
+        if (member.classRef!.name == "") continue;
         if (member.instancesCurrent == 0) continue;
       }
-      vmService.Class c =
-          await serviceClient.getObject(isolateId, member.classRef.id);
+      vmService.Class c = await serviceClient.getObject(
+          isolateId, member.classRef!.id!) as vmService.Class;
       if (c.location?.script?.uri == null) continue;
-      print("${member.classRef.name}: ${member.instancesCurrent}");
+      print("${member.classRef!.name}: ${member.instancesCurrent}");
     }
   }
 
@@ -89,32 +88,32 @@
     await waitUntilIsolateIsRunnable(isolateId);
     vmService.AllocationProfile allocationProfile =
         await serviceClient.getAllocationProfile(isolateId);
-    for (vmService.ClassHeapStats member in allocationProfile.members) {
-      if (member.classRef.name != filter) continue;
-      vmService.Class c =
-          await serviceClient.getObject(isolateId, member.classRef.id);
+    for (vmService.ClassHeapStats member in allocationProfile.members!) {
+      if (member.classRef!.name != filter) continue;
+      vmService.Class c = await serviceClient.getObject(
+          isolateId, member.classRef!.id!) as vmService.Class;
       if (c.location?.script?.uri == null) continue;
-      print("${member.classRef.name}: ${member.instancesCurrent}");
-      print(c.location.script.uri);
+      print("${member.classRef!.name}: ${member.instancesCurrent}");
+      print(c.location!.script!.uri);
 
       vmService.InstanceSet instances = await serviceClient.getInstances(
-          isolateId, member.classRef.id, 10000);
+          isolateId, member.classRef!.id!, 10000);
       int instanceNum = 0;
-      for (vmService.ObjRef instance in instances.instances) {
+      for (vmService.ObjRef instance in instances.instances!) {
         instanceNum++;
         vmService.Obj receivedObject =
-            await serviceClient.getObject(isolateId, instance.id);
+            await serviceClient.getObject(isolateId, instance.id!);
         if (receivedObject is! vmService.Instance) continue;
         vmService.Instance object = receivedObject;
-        for (vmService.BoundField field in object.fields) {
-          if (field.decl.name == fieldName) {
+        for (vmService.BoundField field in object.fields!) {
+          if (field.decl!.name == fieldName) {
             if (field.value is vmService.Sentinel) continue;
             vmService.Obj receivedValue =
                 await serviceClient.getObject(isolateId, field.value.id);
             if (receivedValue is! vmService.Instance) continue;
-            String value = (receivedValue as vmService.Instance).valueAsString;
+            String value = receivedValue.valueAsString!;
             if (!fieldValues.contains(value)) continue;
-            print("${instanceNum}: ${field.decl.name}: "
+            print("${instanceNum}: ${field.decl!.name}: "
                 "${value} --- ${instance.id}");
           }
         }
@@ -127,29 +126,29 @@
     await waitUntilIsolateIsRunnable(isolateId);
     vmService.AllocationProfile allocationProfile =
         await serviceClient.getAllocationProfile(isolateId);
-    for (vmService.ClassHeapStats member in allocationProfile.members) {
-      if (member.classRef.name != filter) continue;
-      vmService.Class c =
-          await serviceClient.getObject(isolateId, member.classRef.id);
+    for (vmService.ClassHeapStats member in allocationProfile.members!) {
+      if (member.classRef!.name != filter) continue;
+      vmService.Class c = await serviceClient.getObject(
+          isolateId, member.classRef!.id!) as vmService.Class;
       print("Found ${c.name} (location: ${c.location})");
-      print("${member.classRef.name}: "
+      print("${member.classRef!.name}: "
           "(instancesCurrent: ${member.instancesCurrent})");
       print("");
 
       vmService.InstanceSet instances = await serviceClient.getInstances(
-          isolateId, member.classRef.id, 10000);
-      print(" => Got ${instances.instances.length} instances");
+          isolateId, member.classRef!.id!, 10000);
+      print(" => Got ${instances.instances!.length} instances");
       print("");
 
-      for (vmService.ObjRef instance in instances.instances) {
+      for (vmService.ObjRef instance in instances.instances!) {
         vmService.Obj receivedObject =
-            await serviceClient.getObject(isolateId, instance.id);
+            await serviceClient.getObject(isolateId, instance.id!);
         print("Instance: $receivedObject");
         vmService.RetainingPath retainingPath =
-            await serviceClient.getRetainingPath(isolateId, instance.id, 1000);
+            await serviceClient.getRetainingPath(isolateId, instance.id!, 1000);
         print("Retaining path: (length ${retainingPath.length}");
-        for (int i = 0; i < retainingPath.elements.length; i++) {
-          print("  [$i] = ${retainingPath.elements[i]}");
+        for (int i = 0; i < retainingPath.elements!.length; i++) {
+          print("  [$i] = ${retainingPath.elements![i]}");
         }
 
         print("");
diff --git a/pkg/front_end/test/vm_service_heap_helper.dart b/pkg/front_end/test/vm_service_heap_helper.dart
index ac5e8d9..1511a51 100644
--- a/pkg/front_end/test/vm_service_heap_helper.dart
+++ b/pkg/front_end/test/vm_service_heap_helper.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import "vm_service_helper.dart" as vmService;
 
 class VMServiceHeapHelperSpecificExactLeakFinder
@@ -22,13 +20,13 @@
   }) {
     if (interests.isEmpty) throw "Empty list of interests given";
     for (Interest interest in interests) {
-      Map<String, List<String>> classToFields = _interests[interest.uri];
+      Map<String, List<String>>? classToFields = _interests[interest.uri];
       if (classToFields == null) {
         classToFields = Map<String, List<String>>();
         _interests[interest.uri] = classToFields;
       }
       _interestsClassNames.add(interest.className);
-      List<String> fields = classToFields[interest.className];
+      List<String>? fields = classToFields[interest.className];
       if (fields == null) {
         fields = <String>[];
         classToFields[interest.className] = fields;
@@ -36,12 +34,12 @@
       fields.addAll(interest.fieldNames);
     }
     for (Interest interest in prettyPrints) {
-      Map<String, List<String>> classToFields = _prettyPrints[interest.uri];
+      Map<String, List<String>>? classToFields = _prettyPrints[interest.uri];
       if (classToFields == null) {
         classToFields = Map<String, List<String>>();
         _prettyPrints[interest.uri] = classToFields;
       }
-      List<String> fields = classToFields[interest.className];
+      List<String>? fields = classToFields[interest.className];
       if (fields == null) {
         fields = <String>[];
         classToFields[interest.className] = fields;
@@ -51,20 +49,20 @@
   }
 
   Future<void> pause() async {
-    await serviceClient.pause(_isolateRef.id);
+    await serviceClient.pause(_isolateRef.id!);
   }
 
-  vmService.VM _vm;
-  vmService.IsolateRef _isolateRef;
-  int _iterationNumber;
+  late vmService.VM _vm;
+  late vmService.IsolateRef _isolateRef;
+  late int _iterationNumber;
   int get iterationNumber => _iterationNumber;
 
   /// Best effort check if the isolate is idle.
   Future<bool> isIdle() async {
-    dynamic tmp = await serviceClient.getIsolate(_isolateRef.id);
+    dynamic tmp = await serviceClient.getIsolate(_isolateRef.id!);
     if (tmp is vmService.Isolate) {
       vmService.Isolate isolate = tmp;
-      return isolate.pauseEvent.topFrame == null;
+      return isolate.pauseEvent!.topFrame == null;
     }
     return false;
   }
@@ -72,44 +70,44 @@
   @override
   Future<void> run() async {
     _vm = await serviceClient.getVM();
-    if (_vm.isolates.length == 0) {
+    if (_vm.isolates!.length == 0) {
       print("Didn't get any isolates. Will wait 1 second and retry.");
       await Future.delayed(new Duration(seconds: 1));
       _vm = await serviceClient.getVM();
     }
-    if (_vm.isolates.length != 1) {
-      throw "Expected 1 isolate, got ${_vm.isolates.length}";
+    if (_vm.isolates!.length != 1) {
+      throw "Expected 1 isolate, got ${_vm.isolates!.length}";
     }
-    _isolateRef = _vm.isolates.single;
-    await forceGC(_isolateRef.id);
+    _isolateRef = _vm.isolates!.single;
+    await forceGC(_isolateRef.id!);
 
-    assert(await isPausedAtStart(_isolateRef.id));
-    await serviceClient.resume(_isolateRef.id);
+    assert(await isPausedAtStart(_isolateRef.id!));
+    await serviceClient.resume(_isolateRef.id!);
 
     _iterationNumber = 1;
     while (true) {
       if (!shouldDoAnotherIteration(_iterationNumber)) break;
-      await waitUntilPaused(_isolateRef.id);
+      await waitUntilPaused(_isolateRef.id!);
       print("Iteration: #$_iterationNumber");
 
       Stopwatch stopwatch = new Stopwatch()..start();
 
       vmService.AllocationProfile allocationProfile =
-          await forceGC(_isolateRef.id);
+          await forceGC(_isolateRef.id!);
       print("Forced GC in ${stopwatch.elapsedMilliseconds} ms");
 
       stopwatch.reset();
       List<Leak> leaks = [];
-      for (vmService.ClassHeapStats member in allocationProfile.members) {
-        if (_interestsClassNames.contains(member.classRef.name)) {
-          vmService.Class c =
-              await serviceClient.getObject(_isolateRef.id, member.classRef.id);
-          String uriString = c.location?.script?.uri;
+      for (vmService.ClassHeapStats member in allocationProfile.members!) {
+        if (_interestsClassNames.contains(member.classRef!.name)) {
+          vmService.Class c = (await serviceClient.getObject(
+              _isolateRef.id!, member.classRef!.id!)) as vmService.Class;
+          String? uriString = c.location?.script?.uri;
           if (uriString == null) continue;
           Uri uri = Uri.parse(uriString);
-          Map<String, List<String>> uriInterest = _interests[uri];
+          Map<String, List<String>>? uriInterest = _interests[uri];
           if (uriInterest == null) continue;
-          List<String> fieldsForClass = uriInterest[c.name];
+          List<String>? fieldsForClass = uriInterest[c.name];
           if (fieldsForClass == null) continue;
 
           List<String> fieldsForClassPrettyPrint = fieldsForClass;
@@ -117,11 +115,11 @@
           uriInterest = _prettyPrints[uri];
           if (uriInterest != null) {
             if (uriInterest[c.name] != null) {
-              fieldsForClassPrettyPrint = uriInterest[c.name];
+              fieldsForClassPrettyPrint = uriInterest[c.name]!;
             }
           }
 
-          leaks.addAll(await _findLeaks(_isolateRef, member.classRef,
+          leaks.addAll(await _findLeaks(_isolateRef, member.classRef!,
               fieldsForClass, fieldsForClassPrettyPrint));
         }
       }
@@ -138,7 +136,7 @@
 
       print("Looked for leaks in ${stopwatch.elapsedMilliseconds} ms");
 
-      await serviceClient.resume(_isolateRef.id);
+      await serviceClient.resume(_isolateRef.id!);
       _iterationNumber++;
     }
   }
@@ -149,7 +147,7 @@
       List<String> fieldsForClass,
       List<String> fieldsForClassPrettyPrint) async {
     // Use undocumented (/ private?) method to get all instances of this class.
-    vmService.InstanceRef instancesAsList = await serviceClient.callMethod(
+    vmService.InstanceRef instancesAsList = (await serviceClient.callMethod(
       "_getInstancesAsArray",
       isolateId: isolateRef.id,
       args: {
@@ -157,7 +155,7 @@
         "includeSubclasses": false,
         "includeImplementors": false,
       },
-    );
+    )) as vmService.InstanceRef;
 
     // Create dart code that `toString`s a class instance according to
     // the fields given as wanting printed. Both for finding duplicates (1) and
@@ -165,14 +163,14 @@
     // them) (2).
 
     // 1:
-    String fieldsToStringCode = classRef.name +
+    String fieldsToStringCode = classRef.name! +
         "[" +
         fieldsForClass
             .map((value) => "$value: \"\${element.$value}\"")
             .join(", ") +
         "]";
     // 2:
-    String fieldsToStringPrettyPrintCode = classRef.name +
+    String fieldsToStringPrettyPrintCode = classRef.name! +
         "[" +
         fieldsForClassPrettyPrint
             .map((value) => "$value: \"\${element.$value}\"")
@@ -182,9 +180,9 @@
     // Expression evaluation to find duplicates: Put all entries into a map
     // indexed by the `toString` code created above, mapping to list of that
     // data.
-    vmService.InstanceRef mappedData = await serviceClient.evaluate(
-      isolateRef.id,
-      instancesAsList.id,
+    vmService.InstanceRef mappedData = (await serviceClient.evaluate(
+      isolateRef.id!,
+      instancesAsList.id!,
       """
           this
               .fold({}, (dynamic index, dynamic element) {
@@ -194,22 +192,22 @@
                 return index;
               })
         """,
-    );
+    )) as vmService.InstanceRef;
     // Expression calculation to find if any of the lists created as values
     // above contains more than one entry (i.e. there's a duplicate).
-    vmService.InstanceRef duplicatesLengthRef = await serviceClient.evaluate(
-      isolateRef.id,
-      mappedData.id,
+    vmService.InstanceRef duplicatesLengthRef = (await serviceClient.evaluate(
+      isolateRef.id!,
+      mappedData.id!,
       """
           this
               .values
               .where((dynamic element) => (element.length > 1) as bool)
               .length
         """,
-    );
-    vmService.Instance duplicatesLength =
-        await serviceClient.getObject(isolateRef.id, duplicatesLengthRef.id);
-    int duplicates = int.tryParse(duplicatesLength.valueAsString);
+    )) as vmService.InstanceRef;
+    vmService.Instance duplicatesLength = (await serviceClient.getObject(
+        isolateRef.id!, duplicatesLengthRef.id!)) as vmService.Instance;
+    int? duplicates = int.tryParse(duplicatesLength.valueAsString!);
     if (duplicates != 0) {
       // There are duplicates. Expression calculation to encode the duplication
       // data (both the string that caused it to be a duplicate and the pretty
@@ -219,9 +217,9 @@
       // e.g. encode the string "string" as "6:string" (length 6, string),
       // and the list ["foo", "bar"] as "2:3:foo:3:bar" (2 entries, length 3,
       // foo, length 3, bar).
-      vmService.ObjRef duplicatesDataRef = await serviceClient.evaluate(
-        isolateRef.id,
-        mappedData.id,
+      vmService.ObjRef duplicatesDataRef = (await serviceClient.evaluate(
+        isolateRef.id!,
+        mappedData.id!,
         """
           this
               .entries
@@ -237,11 +235,11 @@
             return "\${keyPart}:\${valuePart1}:\${valuePart2}";
           }).join(":")
           """,
-      );
+      )) as vmService.ObjRef;
       if (duplicatesDataRef is! vmService.InstanceRef) {
         if (duplicatesDataRef is vmService.ErrorRef) {
-          vmService.Error error = await serviceClient.getObject(
-              isolateRef.id, duplicatesDataRef.id);
+          vmService.Error error = (await serviceClient.getObject(
+              isolateRef.id!, duplicatesDataRef.id!)) as vmService.Error;
           throw "Leak found, but trying to evaluate pretty printing "
               "didn't go as planned.\n"
               "Got error with message "
@@ -254,9 +252,9 @@
         }
       }
 
-      vmService.Instance duplicatesData =
-          await serviceClient.getObject(isolateRef.id, duplicatesDataRef.id);
-      String encodedData = duplicatesData.valueAsString;
+      vmService.Instance duplicatesData = (await serviceClient.getObject(
+          isolateRef.id!, duplicatesDataRef.id!)) as vmService.Instance;
+      String encodedData = duplicatesData.valueAsString!;
       try {
         return parseEncodedLeakString(encodedData);
       } catch (e) {
diff --git a/pkg/front_end/test/vm_service_heap_helper_test.dart b/pkg/front_end/test/vm_service_heap_helper_test.dart
index 8a7df50..3879d88 100644
--- a/pkg/front_end/test/vm_service_heap_helper_test.dart
+++ b/pkg/front_end/test/vm_service_heap_helper_test.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:async';
 import 'dart:developer';
 import 'dart:io';
@@ -144,9 +142,9 @@
   Completer<List<String>> completer = new Completer<List<String>>();
 
   LeakFinderTest({
-    List<helper.Interest> interests,
-    List<helper.Interest> prettyPrints,
-    bool throwOnPossibleLeak,
+    required List<helper.Interest> interests,
+    required List<helper.Interest> prettyPrints,
+    required bool throwOnPossibleLeak,
   }) : super(
             interests: interests,
             prettyPrints: prettyPrints,
diff --git a/pkg/front_end/test/vm_service_helper.dart b/pkg/front_end/test/vm_service_helper.dart
index c796b41..f2d3cb6 100644
--- a/pkg/front_end/test/vm_service_helper.dart
+++ b/pkg/front_end/test/vm_service_helper.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import "dart:convert";
 import "dart:io";
 
@@ -14,7 +12,7 @@
 export "package:vm_service/vm_service_io.dart";
 
 class VMServiceHelper {
-  vmService.VmService _serviceClient;
+  late vmService.VmService _serviceClient;
   vmService.VmService get serviceClient => _serviceClient;
 
   VMServiceHelper();
@@ -34,7 +32,7 @@
   Future<bool> waitUntilPaused(String isolateId) async {
     int nulls = 0;
     while (true) {
-      bool result = await isPaused(isolateId);
+      bool? result = await isPaused(isolateId);
       if (result == null) {
         nulls++;
         if (nulls > 5) {
@@ -50,11 +48,11 @@
     }
   }
 
-  Future<bool> isPaused(String isolateId) async {
+  Future<bool?> isPaused(String isolateId) async {
     dynamic tmp = await _serviceClient.getIsolate(isolateId);
     if (tmp is vmService.Isolate) {
       vmService.Isolate isolate = tmp;
-      if (isolate.pauseEvent.kind != "Resume") return true;
+      if (isolate.pauseEvent!.kind != "Resume") return true;
       return false;
     }
     return null;
@@ -64,7 +62,7 @@
     dynamic tmp = await _serviceClient.getIsolate(isolateId);
     if (tmp is vmService.Isolate) {
       vmService.Isolate isolate = tmp;
-      return isolate.pauseEvent.kind == "PauseStart";
+      return isolate.pauseEvent!.kind == "PauseStart";
     }
     return false;
   }
@@ -73,7 +71,7 @@
     dynamic tmp = await _serviceClient.getIsolate(isolateId);
     if (tmp is vmService.Isolate) {
       vmService.Isolate isolate = tmp;
-      return isolate.pauseEvent.kind == "PauseExit";
+      return isolate.pauseEvent!.kind == "PauseExit";
     }
     return false;
   }
@@ -91,13 +89,13 @@
         rethrow;
       }
       if (allocationProfile.dateLastServiceGC != null &&
-          allocationProfile.dateLastServiceGC >= expectGcAfter) {
+          allocationProfile.dateLastServiceGC! >= expectGcAfter) {
         return allocationProfile;
       }
     }
   }
 
-  Future<bool> isIsolateRunnable(String isolateId) async {
+  Future<bool?> isIsolateRunnable(String isolateId) async {
     dynamic tmp = await _serviceClient.getIsolate(isolateId);
     if (tmp is vmService.Isolate) {
       vmService.Isolate isolate = tmp;
@@ -109,7 +107,7 @@
   Future<void> waitUntilIsolateIsRunnable(String isolateId) async {
     int nulls = 0;
     while (true) {
-      bool result = await isIsolateRunnable(isolateId);
+      bool? result = await isIsolateRunnable(isolateId);
       if (result == null) {
         nulls++;
         if (nulls > 5) {
@@ -127,11 +125,11 @@
 
   Future<String> getIsolateId() async {
     vmService.VM vm = await _serviceClient.getVM();
-    if (vm.isolates.length != 1) {
-      throw "Expected 1 isolate, got ${vm.isolates.length}";
+    if (vm.isolates!.length != 1) {
+      throw "Expected 1 isolate, got ${vm.isolates!.length}";
     }
-    vmService.IsolateRef isolateRef = vm.isolates.single;
-    return isolateRef.id;
+    vmService.IsolateRef isolateRef = vm.isolates!.single;
+    return isolateRef.id!;
   }
 }
 
@@ -150,14 +148,14 @@
 }
 
 abstract class LaunchingVMServiceHelper extends VMServiceHelper {
-  Process _process;
+  late Process _process;
   Process get process => _process;
 
   bool _started = false;
 
   Future<void> start(List<String> scriptAndArgs,
-      {void stdoutReceiver(String line),
-      void stderrReceiver(String line)}) async {
+      {void Function(String line)? stdoutReceiver,
+      void Function(String line)? stderrReceiver}) async {
     if (_started) throw "Already started";
     _started = true;
     _process = await Process.start(
diff --git a/pkg/front_end/tool/generate_ast_coverage.dart b/pkg/front_end/tool/generate_ast_coverage.dart
index d3a299b..833e707 100644
--- a/pkg/front_end/tool/generate_ast_coverage.dart
+++ b/pkg/front_end/tool/generate_ast_coverage.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:io';
 
 import 'ast_model.dart';
@@ -21,7 +19,7 @@
   new File.fromUri(output).writeAsStringSync(result);
 }
 
-Future<String> generateAstCoverage(Uri repoDir, [AstModel astModel]) async {
+Future<String> generateAstCoverage(Uri repoDir, [AstModel? astModel]) async {
   astModel ??= await deriveAstModel(repoDir);
   return generateVisitor(astModel, new CoverageVisitorStrategy());
 }
@@ -47,7 +45,7 @@
 
   @override
   void handleVisit(AstModel astModel, AstClass astClass, StringBuffer sb) {
-    AstClass superAstClass = astClass.superclass;
+    AstClass? superAstClass = astClass.superclass;
     while (superAstClass != null && !superAstClass.isInterchangeable) {
       superAstClass = superAstClass.superclass;
     }
@@ -61,13 +59,13 @@
   @override
   void handleVisitReference(
       AstModel astModel, AstClass astClass, StringBuffer sb) {
-    AstClass superAstClass = astClass.superclass;
+    AstClass? superAstClass = astClass.superclass;
     while (superAstClass != null && !superAstClass.isInterchangeable) {
       superAstClass = superAstClass.superclass;
     }
     if (superAstClass == astModel.constantClass) {
       // Constants are only visited as references.
-      String innerName = superAstClass.name;
+      String innerName = superAstClass!.name;
       (nestedClassNames[innerName] ??= {}).add(astClass.name);
       sb.writeln('''
         visited.add(${innerName}Kind.${astClass.name});
diff --git a/pkg/test_runner/lib/src/compiler_configuration.dart b/pkg/test_runner/lib/src/compiler_configuration.dart
index cb968a9..a32b394 100644
--- a/pkg/test_runner/lib/src/compiler_configuration.dart
+++ b/pkg/test_runner/lib/src/compiler_configuration.dart
@@ -828,6 +828,9 @@
       if (_configuration.useQemu) '--no-use-integer-division',
       if (arguments.contains('--print-flow-graph-optimized'))
         '--redirect-isolate-log-to=$tempDir/out.il',
+      if (arguments.contains('--print-flow-graph-optimized') &&
+          (_configuration.isMinified || arguments.contains('--obfuscate')))
+        '--save-obfuscation_map=$tempDir/renames.json',
       ..._replaceDartFiles(arguments, tempKernelFile(tempDir)),
     ];
 
@@ -844,6 +847,7 @@
     var args = [
       arguments.firstWhere((arg) => arg.endsWith('_il_test.dart')),
       '$tempDir/out.il',
+      if (arguments.contains('--obfuscate')) '$tempDir/renames.json',
     ];
 
     return CompilationCommand('compare_il', tempDir, bootstrapDependencies(),
@@ -965,9 +969,10 @@
   List<String> computeCompilerArguments(
       TestFile testFile, List<String> vmOptions, List<String> args) {
     return [
-      if (testFile.ilMatches.isNotEmpty) ...[
+      if (testFile.isVmIntermediateLanguageTest) ...[
         '--print-flow-graph-optimized',
-        '--print-flow-graph-filter=${testFile.ilMatches.join(',')}'
+        '--print-flow-graph-as-json',
+        '--print-flow-graph-filter=@pragma',
       ],
       if (_enableAsserts) '--enable_asserts',
       ...filterVmOptions(vmOptions),
diff --git a/pkg/test_runner/lib/src/test_file.dart b/pkg/test_runner/lib/src/test_file.dart
index 8b61d68..3c71e5b 100644
--- a/pkg/test_runner/lib/src/test_file.dart
+++ b/pkg/test_runner/lib/src/test_file.dart
@@ -211,10 +211,7 @@
       throw FormatException('Unknown feature "$name" in test $filePath');
     });
 
-    var ilMatches = filePath.endsWith('_il_test.dart')
-        ? _parseStringOption(filePath, contents, r'MatchIL\[AOT\]',
-            allowMultiple: true)
-        : const <String>[];
+    final isVmIntermediateLanguageTest = filePath.endsWith('_il_test.dart');
 
     // VM options.
     var vmOptions = <List<String>>[];
@@ -341,7 +338,7 @@
         sharedObjects: sharedObjects,
         otherResources: otherResources,
         experiments: experiments,
-        ilMatches: ilMatches);
+        isVmIntermediateLanguageTest: isVmIntermediateLanguageTest);
   }
 
   /// A special fake test file for representing a VM unit test written in C++.
@@ -363,7 +360,7 @@
         sharedObjects = [],
         otherResources = [],
         experiments = [],
-        ilMatches = [],
+        isVmIntermediateLanguageTest = false,
         super(null, null, []);
 
   TestFile._(Path suiteDirectory, Path path, List<StaticError> expectedErrors,
@@ -384,7 +381,7 @@
       this.sharedObjects,
       this.otherResources,
       this.experiments,
-      this.ilMatches = const <String>[]})
+      this.isVmIntermediateLanguageTest = false})
       : super(suiteDirectory, path, expectedErrors) {
     assert(!isMultitest || dartOptions.isEmpty);
   }
@@ -403,6 +400,7 @@
   final bool hasRuntimeError;
   final bool hasStaticWarning;
   final bool hasCrash;
+  final bool isVmIntermediateLanguageTest;
 
   /// The features that a test configuration must support in order to run this
   /// test.
@@ -411,9 +409,6 @@
   /// requirements, the test is implicitly skipped.
   final List<Feature> requirements;
 
-  /// List of functions which will have their IL verified (in AOT mode).
-  final List<String> ilMatches;
-
   final List<String> sharedOptions;
   final List<String> dartOptions;
   final List<String> dart2jsOptions;
@@ -479,6 +474,7 @@
   final bool hasStaticWarning;
   final bool hasSyntaxError;
   bool get hasCrash => _origin.hasCrash;
+  bool get isVmIntermediateLanguageTest => _origin.isVmIntermediateLanguageTest;
 
   _MultitestFile(this._origin, Path path, this.multitestKey,
       List<StaticError> expectedErrors,
@@ -493,7 +489,6 @@
   String get packages => _origin.packages;
 
   List<Feature> get requirements => _origin.requirements;
-  List<String> get ilMatches => _origin.ilMatches;
   List<String> get dart2jsOptions => _origin.dart2jsOptions;
   List<String> get dartOptions => _origin.dartOptions;
   List<String> get ddcOptions => _origin.ddcOptions;
diff --git a/pkg/vm/bin/compare_il.dart b/pkg/vm/bin/compare_il.dart
index 9d9febd..8196a5e 100644
--- a/pkg/vm/bin/compare_il.dart
+++ b/pkg/vm/bin/compare_il.dart
@@ -5,185 +5,157 @@
 // This is a helper script which performs IL matching for AOT IL tests.
 // See runtime/docs/infra/il_tests.md for more information.
 
+import 'dart:collection';
+import 'dart:convert';
 import 'dart:io';
+import 'dart:mirrors';
 
-void main(List<String> args) {
-  if (args.length != 2) {
-    throw 'Usage: compare_il <*_il_test.dart> <output.il>';
+import 'package:collection/collection.dart';
+
+import 'package:vm/testing/il_matchers.dart';
+
+void main(List<String> args) async {
+  getName = MirrorSystem.getName;
+
+  if (args.length < 2 || args.length > 3) {
+    throw 'Usage: compare_il <*_il_test.dart> <output.il> [<renames.json>]';
   }
 
   final testFile = args[0];
   final ilFile = args[1];
+  final renamesFile = args.length == 3 ? args[2] : null;
 
-  final graphs = _extractGraphs(ilFile);
+  final rename = _loadRenames(renamesFile);
+  final graphs = _loadGraphs(ilFile, rename);
+  final tests = await _loadTestCases(testFile);
 
-  final expectations = _extractExpectations(testFile);
+  Map<String, FlowGraph> findMatchingGraphs(String name) {
+    final suffix = '_${rename(name)}';
+    return graphs.entries.firstWhere((f) => f.key.contains(suffix)).value;
+  }
 
-  for (var expectation in expectations.entries) {
-    // Find a graph for this expectation. We expect that function names are
-    // unique enough to identify a specific graph.
-    final graph =
-        graphs.entries.singleWhere((e) => e.key.contains(expectation.key));
-
-    // Extract the list of opcodes, ignoring irrelevant things like
-    // ParallelMove.
-    final gotOpcodesIgnoringMoves = graph.value
-        .where((instr) => instr.opcode != 'ParallelMove')
-        .map((instr) => instr.opcode)
-        .toList();
-
-    // Check that expectations are the prefix of gotOpcodesIgnoringMoves.
-    print('Matching ${graph.key}');
-    for (var i = 0; i < expectation.value.length; i++) {
-      final gotOpcode = gotOpcodesIgnoringMoves[i];
-      final expectedOpcode = expectation.value[i];
-      if (gotOpcode != expectedOpcode) {
-        throw 'Failed to match graph of ${graph.key} to '
-            'expectations for ${expectation.key} at instruction ${i}: '
-            'got ${gotOpcode} expected ${expectedOpcode}';
-      }
-    }
-    print('... ok');
+  for (var test in tests) {
+    test.run(findMatchingGraphs(test.name));
   }
 
   exit(0); // Success.
 }
 
-// IL instruction extracted from flow graph dump.
-class Instruction {
-  final String raw;
+class TestCase {
+  final String name;
+  final String phasesFilter;
+  final LibraryMirror library;
 
-  Instruction(this.raw);
+  late final phases =
+      phasesFilter.split(',').expand(_expandPhasePattern).toList();
 
-  String get opcode {
-    final match = instructionPattern.firstMatch(raw)!;
-    final op = match.namedGroup('opcode')!;
-    final blockType = match.namedGroup('block_type');
+  TestCase({
+    required this.name,
+    required this.phasesFilter,
+    required this.library,
+  });
 
-    // Handle blocks which look like "B%d[%s]".
-    if (blockType != null) {
-      return blockTypes[blockType]!;
-    }
-
-    // Handle parallel moves specially.
-    if (op.startsWith('ParallelMove')) {
-      return 'ParallelMove';
-    }
-
-    // Handle branches.
-    if (op.startsWith(branchIfPrefix)) {
-      return 'Branch(${op.substring(branchIfPrefix.length)})';
-    }
-
-    // Normal instruction.
-    return op;
+  void run(Map<String, FlowGraph> graphs) {
+    print('matching IL (${phases.join(', ')}) for $name');
+    library.invoke(MirrorSystem.getSymbol('matchIL\$$name'),
+        phases.map((phase) => graphs[phase]!).toList());
+    print('... ok');
   }
 
-  @override
-  String toString() => 'Instruction($opcode)';
+  /// Parses phase filter components (same format as --compiler-passes flag).
+  static List<String> _expandPhasePattern(String pattern) {
+    bool printBefore = false, printAfter = false;
+    switch (pattern[0]) {
+      case '[':
+        printBefore = true;
+        break;
+      case ']':
+        printAfter = true;
+        break;
+      case '*':
+        printBefore = printAfter = true;
+        break;
+    }
 
-  static final instructionPattern = RegExp(
-      r'^\s*\d+:\s+(v\d+ <- )?(?<opcode>[^:[(]+(?<block_type>\[[\w ]+\])?)');
+    final phaseName =
+        (printBefore || printAfter) ? pattern.substring(1) : pattern;
 
-  static const blockTypes = {
-    '[join]': 'JoinEntry',
-    '[target]': 'TargetEntry',
-    '[graph]': 'GraphEntry',
-    '[function entry]': 'FunctionEntry'
-  };
+    if (!printBefore && !printAfter) {
+      printAfter = true;
+    }
 
-  static const branchIfPrefix = 'Branch if ';
+    return [
+      if (printBefore) 'Before $phaseName',
+      if (printAfter) 'After $phaseName',
+    ];
+  }
 }
 
-Map<String, List<Instruction>> _extractGraphs(String ilFile) {
-  final graphs = <String, List<Instruction>>{};
+/// Extracts test cases from the given file by looking for functions
+/// marked with @pragma('vm:testing:print-flow-graph', ...).
+Future<Set<TestCase>> _loadTestCases(String testFile) async {
+  final mirrorSystem = currentMirrorSystem();
+  final library =
+      await mirrorSystem.isolate.loadUri(File(testFile).absolute.uri);
 
-  final reader = LineReader(ilFile);
+  pragma? getPragma(DeclarationMirror decl, String name) => decl.metadata
+      .map((m) => m.reflectee)
+      .whereType<pragma>()
+      .firstWhereOrNull((p) => p.name == name);
 
-  var instructions = <Instruction>[];
-  while (reader.hasMore) {
-    if (reader.testNext('*** BEGIN CFG')) {
-      reader.next(); // Skip phase name.
-      final functionName = reader.next();
-      while (!reader.testNext('*** END CFG')) {
-        var curr = reader.next();
+  final cases = LinkedHashSet<TestCase>(
+    equals: (a, b) => a.name == b.name,
+    hashCode: (a) => a.name.hashCode,
+  );
 
-        // If instruction line ends with '{' search for a matching '}' (it will
-        // be on its own line).
-        if (curr.endsWith('{')) {
-          do {
-            curr += '\n' + reader.current;
-          } while (reader.next() != '}');
-        }
-
-        instructions.add(Instruction(curr));
-      }
-
-      graphs[functionName] = instructions;
-      instructions = <Instruction>[];
-    } else {
-      reader.next();
+  void processDeclaration(DeclarationMirror decl) {
+    final p = getPragma(decl, 'vm:testing:print-flow-graph');
+    if (p != null) {
+      final name = MirrorSystem.getName(decl.simpleName);
+      final added = cases.add(TestCase(
+        name: name,
+        phasesFilter: (p.options as String?) ?? 'AllocateRegisters',
+        library: library,
+      ));
+      if (!added) throw 'duplicate test case with name $name';
     }
   }
 
+  for (var decl in library.declarations.values) {
+    if (decl is ClassMirror) {
+      decl.declarations.values.forEach(processDeclaration);
+    } else {
+      processDeclaration(decl);
+    }
+  }
+
+  return cases;
+}
+
+Map<String, Map<String, FlowGraph>> _loadGraphs(String ilFile, Renamer rename) {
+  final graphs = <String, Map<String, FlowGraph>>{};
+
+  for (var graph in File(ilFile).readAsLinesSync()) {
+    final m = jsonDecode(graph) as Map<String, dynamic>;
+    graphs.putIfAbsent(m['f'], () => {})[m['p']] =
+        FlowGraph(m['b'], m['desc'], rename: rename);
+  }
+
   return graphs;
 }
 
-Map<String, List<String>> _extractExpectations(String testFile) {
-  final expectations = <String, List<String>>{};
-
-  final reader = LineReader(testFile);
-
-  final matchILPattern = RegExp(r'^// MatchIL\[AOT\]=(?<value>.*)$');
-  final matcherPattern = RegExp(r'^// __ (?<value>.*)$');
-
-  var matchers = <String>[];
-  while (reader.hasMore) {
-    var functionName = reader.matchNext(matchILPattern);
-    if (functionName != null) {
-      // Read comment block which follows `// MatchIL[AOT]=...`.
-      while (reader.hasMore && reader.current.startsWith('//')) {
-        final match = matcherPattern.firstMatch(reader.next());
-        if (match != null) {
-          matchers.add(match.namedGroup('value')!);
-        }
-      }
-      expectations[functionName] = matchers;
-      matchers = <String>[];
-    } else {
-      reader.next();
-    }
+Renamer _loadRenames(String? renamesFile) {
+  // Load renames map if present.
+  if (renamesFile == null) {
+    return (v) => v;
   }
 
-  return expectations;
-}
+  final list =
+      (jsonDecode(File(renamesFile).readAsStringSync()) as List).cast<String>();
 
-class LineReader {
-  final List<String> lines;
-  int lineno = 0;
+  final renamesMap = <String, String>{
+    for (var i = 0; i < list.length; i += 2) list[i]: list[i + 1],
+  };
 
-  LineReader(String path) : lines = File(path).readAsLinesSync();
-
-  String get current => lines[lineno];
-
-  bool get hasMore => lineno < lines.length;
-
-  String next() {
-    final curr = current;
-    lineno++;
-    return curr;
-  }
-
-  bool testNext(String expected) {
-    if (current == expected) {
-      next();
-      return true;
-    }
-    return false;
-  }
-
-  String? matchNext(RegExp pattern) {
-    final m = pattern.firstMatch(current);
-    return m?.namedGroup('value');
-  }
+  return (v) => renamesMap[v] ?? v;
 }
diff --git a/pkg/vm/lib/testing/il_matchers.dart b/pkg/vm/lib/testing/il_matchers.dart
new file mode 100644
index 0000000..dd5eb8f
--- /dev/null
+++ b/pkg/vm/lib/testing/il_matchers.dart
@@ -0,0 +1,430 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A library to facilitate programmatic matching against flow graphs
+/// collected during IL tests. See runtime/docs/infra/il_tests.md for more
+/// info.
+
+typedef Renamer = String Function(String);
+
+/// Flow graph parsed from --print-flow-graph-as-json output.
+class FlowGraph {
+  final List<dynamic> blocks;
+  final Map<String, InstructionDescriptor> descriptors;
+  final Renamer rename;
+
+  FlowGraph(this.blocks, Map<String, dynamic> desc, {required this.rename})
+      : descriptors = {
+          for (var e in desc.entries)
+            e.key: InstructionDescriptor.fromJson(e.value)
+        };
+
+  /// Match the sequence of blocks in this flow graph against the given
+  /// sequence of matchers: `expected[i]` is expected to match `blocks[i]`,
+  /// but there can be more blocks in the graph than matchers (the suffix is
+  /// ignored).
+  ///
+  /// If [env] is provided it will be used as matching environment, otherwise
+  /// a fresh instance of [Env] will be created and used.
+  ///
+  /// This function returns the populated matching environment.
+  Env match(List<Matcher> expected, {Env? env}) {
+    env ??= Env(rename: rename, descriptors: descriptors);
+
+    for (var i = 0; i < expected.length; i++) {
+      expected[i].match(env, blocks[i]).expectMatched('failed to match');
+    }
+
+    return env;
+  }
+}
+
+class InstructionDescriptor {
+  final List<String> attributes;
+  final Map<String, int> attributeIndex;
+
+  InstructionDescriptor.fromJson(List<dynamic> attrs)
+      : this._(attrs.map((v) => _demangle(v)).toList());
+
+  InstructionDescriptor._(List<String> attrs)
+      : attributes = attrs.cast<String>(),
+        attributeIndex = {for (var i = 0; i < attrs.length; i++) attrs[i]: i};
+
+  static String _demangle(String v) {
+    final prefixLen = v.startsWith('&') ? 1 : 0;
+    final suffixLen = v.endsWith('()') ? 2 : 0;
+    return v.substring(prefixLen, v.length - suffixLen);
+  }
+}
+
+/// Matching environment.
+///
+/// This is fundamentally just a name to id mapping which allows to track
+/// correspondence between names given to some matchers and blocks/instructions
+/// which matched those matchers.
+///
+/// This object is also used to carry around auxiliary information which might
+/// be needed for matching, e.g. [Renamer].
+class Env {
+  final Map<String, InstructionDescriptor> descriptors;
+  final Renamer rename;
+  final Map<String, int> nameToId = {};
+
+  Env({required this.rename, required this.descriptors});
+
+  void bind(String name, Map<String, dynamic> instrOrBlock) {
+    final id = instrOrBlock['v'] ?? instrOrBlock['b'];
+
+    if (nameToId.containsKey(name) && nameToId[name] != id) {
+      throw 'Binding mismatch for $name: got ${nameToId[name]} and $id';
+    }
+
+    nameToId[name] = id;
+  }
+}
+
+abstract class Matcher {
+  /// Try matching this matcher against the given value. Returns
+  /// [MatchStatus.matched] if match succeeded and an instance of
+  /// [MatchStatus.fail] otherwise.
+  MatchStatus match(Env e, dynamic v);
+}
+
+class MatchStatus {
+  final String? message;
+
+  const MatchStatus._matched() : message = null;
+  const MatchStatus.fail(String message) : message = message;
+
+  bool get isMatch => message == null;
+  bool get isFail => message != null;
+
+  static const MatchStatus matched = MatchStatus._matched();
+
+  void expectMatched(String s) {
+    if (message != null) {
+      throw 'Failed to match: $message';
+    }
+  }
+}
+
+/// Matcher which always succeeds.
+class _AnyMatcher implements Matcher {
+  const _AnyMatcher();
+
+  @override
+  MatchStatus match(Env e, v) => MatchStatus.matched;
+
+  @override
+  String toString() {
+    return '*';
+  }
+}
+
+/// Matcher which updates matching environment when it succeeds.
+class _BoundMatcher implements Matcher {
+  final String name;
+  final Matcher nested;
+
+  _BoundMatcher(this.name, this.nested);
+
+  @override
+  MatchStatus match(Env e, dynamic v) {
+    final result = nested.match(e, v);
+    if (result.isMatch) {
+      e.bind(name, v);
+    }
+    return result;
+  }
+
+  @override
+  String toString() {
+    return '$name <- $nested';
+  }
+}
+
+/// Matcher which matches a specified value [v].
+class _EqualsMatcher implements Matcher {
+  final dynamic v;
+
+  _EqualsMatcher(this.v);
+
+  @override
+  MatchStatus match(Env e, v) {
+    if (this.v == v) {
+      return MatchStatus.matched;
+    }
+
+    // Some instructions refer to obfuscated names, try to rename
+    // the expectation and try again.
+    if (this.v is String && v is String && e.rename(this.v) == v) {
+      return MatchStatus.matched;
+    }
+
+    return this.v == v
+        ? MatchStatus.matched
+        : MatchStatus.fail('expected ${this.v} got $v');
+  }
+
+  @override
+  String toString() => '$v';
+}
+
+/// Matcher which matches the value which is equivalent to the binding
+/// with the given [name] in the matching environment.
+///
+/// If this matcher is [binding] then it will populate the binding in the
+/// matching environment if the [name] is not bound yet on the first call
+/// to [match]. Otherwise if the [name] is not bound when [match] is called
+/// an exception will be thrown.
+///
+/// Binding matchers are used when we might see the use of a value before its
+/// definition (e.g. we usually use the name of the block in the `Goto` or
+/// `Branch` before we see the block itself).
+class _RefMatcher implements Matcher {
+  final String name;
+  final bool binding;
+
+  _RefMatcher(this.name, {this.binding = false});
+
+  @override
+  MatchStatus match(Env e, v) {
+    if (e.nameToId.containsKey(name)) {
+      return e.nameToId[name] == v
+          ? MatchStatus.matched
+          : MatchStatus.fail(
+              'expected $name to bind to ${e.nameToId[name]} but got $v');
+    }
+
+    if (!binding) {
+      throw UnimplementedError('Unbound reference to ${name}');
+    }
+
+    e.nameToId[name] = v;
+    return MatchStatus.matched;
+  }
+
+  @override
+  String toString() {
+    return name;
+  }
+}
+
+/// A wrapper which matches a list of matchers against a list of values.
+class _ListMatcher implements Matcher {
+  final List<Matcher> expected;
+
+  _ListMatcher(this.expected);
+
+  @override
+  MatchStatus match(Env e, dynamic got) {
+    if (got is! List) {
+      return MatchStatus.fail('expected List, got ${got.runtimeType}');
+    }
+
+    if (expected.length > got.length) {
+      return MatchStatus.fail(
+          'expected at least ${expected.length} elements got ${got.length}');
+    }
+
+    for (var i = 0; i < expected.length; i++) {
+      final result = expected[i].match(e, got[i]);
+      if (result.isFail) {
+        return MatchStatus.fail(
+            'mismatch at index ${i}, expected ${expected[i]} '
+            'got ${got[i]}: ${result.message}');
+      }
+    }
+
+    if (expected.last is _AnyMatcher || expected.length == got.length) {
+      return MatchStatus.matched;
+    }
+
+    return MatchStatus.fail(
+        'expected exactly ${expected.length} elements got ${got.length}');
+  }
+
+  @override
+  String toString() => '[${expected.join(',')}]';
+}
+
+/// A matcher which matches a block of the specified [kind] and contents.
+///
+/// Contents are specified as a sequence of matchers ([body]). For each of
+/// those matchers a matching block is expected to contain at least one
+/// instruction that matches it. Matching is done in order: first we scan
+/// the block until we find the match for the first matcher in body, then
+/// we continue scanning until we find the match for the second and so on.
+class _BlockMatcher implements Matcher {
+  final String kind;
+  final List<Matcher> body;
+
+  _BlockMatcher(this.kind, [this.body = const []]);
+
+  @override
+  MatchStatus match(Env e, covariant Map<String, dynamic> block) {
+    if (block['o'] != '${kind}Entry') {
+      return MatchStatus.fail(
+          'Expected block of kind ${kind} got ${block['o']} '
+          'when matching B${block['b']}');
+    }
+
+    final gotBody = [...?block['d'], ...?block['is']];
+
+    var matcherIndex = 0;
+    for (int i = 0; i < gotBody.length && matcherIndex < body.length; i++) {
+      if (body[matcherIndex].match(e, gotBody[i]).isMatch) {
+        matcherIndex++;
+      }
+    }
+    if (matcherIndex != body.length) {
+      return MatchStatus.fail('Unmatched instruction: ${body[matcherIndex]} '
+          'in block B${block['b']}');
+    }
+    return MatchStatus.matched;
+  }
+}
+
+/// A matcher for instruction's named attributes.
+///
+/// Attributes are resolved to their indices through [Env.descriptors].
+class _AttributesMatcher implements Matcher {
+  final String op;
+  final Map<String, Matcher> matchers;
+
+  _ListMatcher? impl;
+
+  _AttributesMatcher(this.op, this.matchers);
+
+  @override
+  MatchStatus match(Env e, dynamic v) {
+    impl ??= _ListMatcher(e.descriptors[op]!.attributes
+        .map((name) => matchers[name] ?? const _AnyMatcher())
+        .toList());
+    return impl!.match(e, v);
+  }
+}
+
+/// Matcher which matches an instruction with opcode [op] and properties
+/// specified in [matchers] map.
+class InstructionMatcher implements Matcher {
+  final String op;
+  final Map<String, Matcher> matchers;
+
+  InstructionMatcher(
+      {required String op, List<Matcher>? data, List<Matcher>? inputs})
+      : this._(op: op, matchers: {
+          if (data != null) 'd': _ListMatcher(data),
+          if (inputs != null) 'i': _ListMatcher(inputs),
+        });
+
+  InstructionMatcher._({
+    required this.op,
+    required this.matchers,
+  });
+
+  @override
+  MatchStatus match(Env e, covariant Map<String, dynamic> instr) {
+    if (instr['o'] != op) {
+      return MatchStatus.fail('expected instruction ${op} got ${instr['o']}');
+    }
+
+    for (var entry in matchers.entries) {
+      final result = entry.value.match(e, instr[entry.key]);
+      if (result.isFail) {
+        return result;
+      }
+    }
+
+    return MatchStatus.matched;
+  }
+
+  @override
+  String toString() {
+    return '$op($matchers)';
+  }
+}
+
+/// This class uses `noSuchMethod` to allow writing code like
+///
+/// ```
+/// match.Op(in0, ..., inN, attr0: a0, ..., attrK: aK)
+/// ```
+///
+/// This will produce an instruction matcher which matches opcode `Op` and
+/// expects `in0, ..., inN` to match instructions inputs, while `a0, ...`
+/// matchers are expected to match attributes with names `attr0, ...`.
+class Matchers {
+  _BlockMatcher block(String kind, [List<dynamic> body = const []]) {
+    return _BlockMatcher(kind, List<Matcher>.from(body));
+  }
+
+  final _AnyMatcher any = const _AnyMatcher();
+
+  InstructionMatcher Goto(String dest) =>
+      InstructionMatcher._(op: 'Goto', matchers: {
+        's': _ListMatcher([_blockRef(dest)])
+      });
+
+  InstructionMatcher Branch(InstructionMatcher compare,
+          {String? ifTrue, String? ifFalse}) =>
+      InstructionMatcher._(op: 'Branch', matchers: {
+        'cc': compare,
+        's': _ListMatcher([
+          ifTrue != null ? _blockRef(ifTrue) : any,
+          ifFalse != null ? _blockRef(ifFalse) : any,
+        ]),
+      });
+
+  @override
+  Object? noSuchMethod(Invocation invocation) {
+    final data = {
+      for (var e in invocation.namedArguments.entries)
+        getName(e.key): Matchers._toAttributeMatcher(e.value),
+    };
+    final inputs =
+        invocation.positionalArguments.map(Matchers._toInputMatcher).toList();
+    final op = getName(invocation.memberName);
+    return InstructionMatcher._(op: op, matchers: {
+      if (data.isNotEmpty) 'd': _AttributesMatcher(op, data),
+      if (inputs.isNotEmpty) 'i': _ListMatcher(inputs),
+    });
+  }
+
+  static Matcher _blockRef(String name) => _RefMatcher(name, binding: true);
+
+  static Matcher _toAttributeMatcher(dynamic v) {
+    if (v is Matcher) {
+      return v;
+    } else {
+      return _EqualsMatcher(v);
+    }
+  }
+
+  static Matcher _toInputMatcher(dynamic v) {
+    if (v is Matcher) {
+      return v;
+    } else if (v is String) {
+      return _RefMatcher(v);
+    } else {
+      throw ArgumentError.value(
+          v, 'v', 'Expected either a Matcher or a String (binding name)');
+    }
+  }
+}
+
+/// Extension which enables `'name' << matcher` syntax for creating bound
+/// matchers.
+extension BindingExtension on String {
+  Matcher operator <<(Matcher matcher) {
+    return _BoundMatcher(this, matcher);
+  }
+}
+
+final dynamic match = Matchers();
+
+/// This file should not depend on dart:mirrors because it is imported into
+/// tests, which are compiled in AOT mode. So instead we let compare_il driver
+/// set this field.
+late String Function(Symbol) getName;
diff --git a/pkg/vm/tool/compare_il b/pkg/vm/tool/compare_il
index 65ad55e..4973042 100755
--- a/pkg/vm/tool/compare_il
+++ b/pkg/vm/tool/compare_il
@@ -26,10 +26,6 @@
 
 # TODO(kustermann): For windows as well as for hosts running on arm, our
 # checked-in dart binaries must be adjusted.
-if [[ `uname` == 'Darwin' ]]; then
-  DART="$SDK_DIR/tools/sdks/dart-sdk/bin/dart"
-else
-  DART="$SDK_DIR/tools/sdks/dart-sdk/bin/dart"
-fi
+DART="$SDK_DIR/tools/sdks/dart-sdk/bin/dart"
 
 exec "$DART" $DART_VM_FLAGS "${SDK_DIR}/pkg/vm/bin/compare_il.dart" $@
diff --git a/runtime/docs/infra/il_tests.md b/runtime/docs/infra/il_tests.md
index 8400b6e..94c4500 100644
--- a/runtime/docs/infra/il_tests.md
+++ b/runtime/docs/infra/il_tests.md
@@ -11,76 +11,47 @@
 
 IL tests are placed in files ending with `_il_test.dart`.
 
-Each IL test should contain one or more _IL matching blocks_, which have the
-following format:
+Each IL test should contain one or more of the functions marked with a
+`@pragma('vm:testing:print-flow-graph'[, 'phases filter'])`.
+
+These functions will have their IL dumped at points specified by the
+_phases filter_ (if present, `]AllocateRegisters` by default), which follows
+the same syntax as `--compiler-passes=` flag and dumped IL will be compared
+against the expectations, which are specified programmatically using
+`package:vm/testing/il_matchers.dart` helpers. A function named `foo` has
+its IL expectations in the function called `matchIL$foo` in the same file.
 
 ```dart
-// MatchIL[AOT]=functionName
-//   comment
-// __ op
-//   comment
-// __ op
-// __ op
-// __ op
+import 'package:vm/testing/il_matchers.dart';
+
+@pragma('vm:testing:print-flow-graph')
+void foo() {
+}
+
+/// Expectations for [foo].
+void matchIL$foo(FlowGraph graph) {
+  graph.match([/* expectations */]);
+}
 ```
 
-Each section starts with a `// MatchIL[AOT]=functionName` line which contains
-the name (or a substring of a name) of the function for which IL should be
-matched.
-
-`// MatchIL[AOT]=...` line is followed by some number of comment lines `//`,
-where lines starting with `// __ ` specify _an instruction matcher_ and the rest
-are ignored (they just act as normal comments).
-
-`gen_snapshot` will be instructed (via `--print-flow-graph-optimized` and
-`--print-flow-graph-filter=functionName,...` flags) to dump IL for all
-functions names specified in IL matching blocks.
-
-After that `pkg/vm/tool/compare_il` script will be used to compare the dumps
-to actual expectations: by checking that dumped flow graph starts with the
-expected sequence of commands (ignoring some instructions like `ParallelMove`).
+Actual matching is done by the `pkg/vm/tool/compare_il` script.
 
 ## Example
 
 ```dart
-// MatchIL[AOT]=factorial
-// __ GraphEntry
-// __ FunctionEntry
-// __ CheckStackOverflow
-// __ Branch(EqualityCompare)
 @pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph')
 int factorial(int value) => value == 1 ? value : value * factorial(value - 1);
-```
 
-This test specifies that the graph for `factorial` should start with a sequence
-`GraphEntry`, `FunctionEntry`, `CheckStackOverflow`, `Branch(EqualityCompare)`.
-
-If the graph has a different shape the test will fail, e.g. given the graph
-
-```
-*** BEGIN CFG
-After AllocateRegisters
-==== file:///.../src/dart/sdk/runtime/tests/vm/dart/aot_prefer_equality_comparison_il_test.dart_::_factorial (RegularFunction)
-  0: B0[graph]:0 {
-      v3 <- Constant(#1) [1, 1] T{_Smi}
-      v19 <- UnboxedConstant(#1 int64) T{_Smi}
+void matchIL$factorial(FlowGraph graph) {
+  // Expected a graph which starts with GraphEntry block followed by a
+  // FunctionEntry block. FunctionEntry block should contain a Branch()
+  // instruction, with EqualityCompare as a comparison.
+  graph.match([
+    match.block('Graph'),
+    match.block('Function', [
+      match.Branch(match.EqualityCompare(match.any, match.any, kind: '==')),
+    ]),
+  ]);
 }
-  2: B1[function entry]:2 {
-      v2 <- Parameter(0) [-9223372036854775808, 9223372036854775807] T{int}
-}
-  4:     CheckStackOverflow:8(stack=0, loop=0)
-  5:     ParallelMove rcx <- S+2
-  6:     v17 <- BoxInt64(v2) [-9223372036854775808, 9223372036854775807] T{int}
-  7:     ParallelMove rax <- rax
-  8:     Branch if StrictCompare(===, v17 T{int}, v3) T{bool} goto (3, 4)
-```
-
-we will get:
-
-```
-Unhandled exception:
-Failed to match graph of ==== file:///.../src/dart/sdk/runtime/tests/vm/dart/aot_prefer_equality_comparison_il_test.dart_::_factorial (RegularFunction) to expectations for factorial at instruction 3: got BoxInt64 expected Branch(EqualityCompare)
-#0      main (file:///.../src/dart/sdk/pkg/vm/bin/compare_il.dart:37:9)
-#1      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:285:32)
-#2      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:187:12)
-```
+```
\ No newline at end of file
diff --git a/runtime/tests/vm/dart/aot_prefer_equality_comparison_il_test.dart b/runtime/tests/vm/dart/aot_prefer_equality_comparison_il_test.dart
index 9baf000..8447554 100644
--- a/runtime/tests/vm/dart/aot_prefer_equality_comparison_il_test.dart
+++ b/runtime/tests/vm/dart/aot_prefer_equality_comparison_il_test.dart
@@ -5,14 +5,21 @@
 // Test that we emit EqualityCompare rather than StrictCompare+BoxInt64
 // when comparing non-nullable integer to a Smi.
 
-// MatchIL[AOT]=factorial
-// __ GraphEntry
-// __ FunctionEntry
-// __ CheckStackOverflow
-// __ Branch(EqualityCompare)
+import 'package:vm/testing/il_matchers.dart';
+
 @pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph')
 int factorial(int value) => value == 1 ? value : value * factorial(value - 1);
 
+void matchIL$factorial(FlowGraph graph) {
+  graph.match([
+    match.block('Graph'),
+    match.block('Function', [
+      match.Branch(match.EqualityCompare(match.any, match.any, kind: '==')),
+    ]),
+  ]);
+}
+
 void main() {
   print(factorial(4));
 }
diff --git a/runtime/tests/vm/dart/flutter_regress_91370_il_test.dart b/runtime/tests/vm/dart/flutter_regress_91370_il_test.dart
new file mode 100644
index 0000000..722da15
--- /dev/null
+++ b/runtime/tests/vm/dart/flutter_regress_91370_il_test.dart
@@ -0,0 +1,718 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:vm/testing/il_matchers.dart';
+
+bool shouldPrint = false;
+
+@pragma('vm:never-inline')
+void blackhole(Object v) {
+  if (shouldPrint) {
+    print(v);
+  }
+}
+
+class A {}
+
+class A0 extends A {
+  final double x;
+  final String str;
+
+  A0(this.x, this.str);
+
+  // Use [x] to prevent the field from being shaken out. We would like
+  // [x] to occupy the same location that [A1.str] takes so that
+  // type confusion / incorrect LICM would cause a crash when we load
+  // `o.[A1.str].length` on an object of type [A0]
+  String toString() => 'A0($x)';
+}
+
+class A1 extends A {
+  final String str;
+
+  A1(this.str);
+}
+
+class H<T> {
+  final T data;
+  H(this.data);
+}
+
+abstract class B<T extends A> {
+  final T v;
+
+  B(this.v);
+
+  int load(H<T> h);
+  int loadWithNamedParam({required H<T> h});
+
+  @pragma('vm:never-inline')
+  @pragma('vm:testing:print-flow-graph', '*LICM')
+  int testNarrowingThroughThisCallWithPositionalParam(H<T> h) {
+    var result = 0;
+    for (var i = 0; i < 10; i++) {
+      // We will perform polymorphic inlining of `load` because `this`
+      // is known to be either B0 or B1. In both cases we will have
+      // v.str.length loads fully inlined because inlined bodies
+      // have precise type information for v.
+      // Then we will hoist v.str.length out of the loop past
+      // class-id comparisons generated by the inlining leading
+      // to incorrect code which will crash.
+      result += load(h);
+    }
+    return result;
+  }
+
+  @pragma('vm:never-inline')
+  @pragma('vm:testing:print-flow-graph', '*LICM')
+  int testNarrowingThroughThisCallWithNamedParams(H<T> h) {
+    var result = 0;
+    for (var i = 0; i < 10; i++) {
+      // We will perform polymorphic inlining of `load` because `this`
+      // is known to be either B0 or B1. In both cases we will have
+      // v.str.length loads fully inlined because inlined bodies
+      // have precise type information for v.
+      // Then we will hoist v.str.length out of the loop past
+      // class-id comparisons generated by the inlining leading
+      // to incorrect code which will crash.
+      result += loadWithNamedParam(h: h);
+    }
+    return result;
+  }
+}
+
+class BImpl<T extends A> extends B<T> {
+  BImpl(T v) : super(v);
+
+  // These methods do not matter. They can just return 0.
+  int load(H<T> h) => 0;
+  int loadWithNamedParam({required H<T> h}) => 0;
+}
+
+class B0 extends B<A0> {
+  B0(A0 a) : super(a);
+
+  int load(H<A0> h) {
+    return h.data.str.length;
+  }
+
+  int loadWithNamedParam({String? a, required H<A0> h, String? z}) {
+    return h.data.str.length;
+  }
+}
+
+class B1 extends B<A1> {
+  B1(A1 a) : super(a);
+
+  int load(H<A1> h) {
+    return h.data.str.length;
+  }
+
+  int loadWithNamedParam({String? a, required H<A1> h, String? z}) {
+    return h.data.str.length;
+  }
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph', '*LICM')
+int testNarrowingThroughIsCheckOnSubclass(B b) {
+  int sum = 0;
+  b.v.toString();
+  for (var i = 0; i < 2; i++) {
+    if (b is B1) {
+      sum += b.v.str.length;
+    }
+  }
+  return sum;
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph', '*LICM')
+int testNarrowingThroughIsCheckWithTypeArgPolymorphic(B b) {
+  int sum = 0;
+  final v = b.v;
+  for (var i = 0; i < 2; i++) {
+    if (b is B<A1>) {
+      sum += b.v.str.length;
+    }
+  }
+  blackhole(v);
+  return sum;
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph', '*LICM')
+int testNarrowingThroughIsCheckWithTypeArgMonomorphic(B b) {
+  int sum = 0;
+  final v = b.v;
+  for (var i = 0; i < 2; i++) {
+    if (b is B<A1>) {
+      sum += b.v.str.length;
+    }
+  }
+  blackhole(v);
+  return sum;
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph', '*LICM')
+int testNarrowingThroughAsCheckWithTypeArgPolymorphic(B b) {
+  int sum = 0;
+  final v = b.v;
+  for (var i = 0; i < 2; i++) {
+    // Branch with a side-effect to avoid b as B<A1> hoisting.
+    if (sum == 42) throw '42';
+    sum += (b as B<A1>).v.str.length;
+  }
+  blackhole(v);
+  return sum;
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph', '*LICM')
+int testNarrowingThroughPhiOfAsChecksWithTypeArgPolymorphic(B b) {
+  int sum = 0;
+  final v = b.v;
+  for (var i = 0; i < 2; i++) {
+    // Branch with a side-effect to avoid b as B<A1> hoisting.
+    if (sum == 42) throw '42';
+    sum += (sum == 22 ? b as B<A1> : b as B<A1>).v.str.length;
+  }
+  blackhole(v);
+  return sum;
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph', '*LICM')
+int testNarrowingThroughIndexedLoadFromGrowableArray(List<A> l) {
+  int sum = 0;
+  final v = l[0];
+  for (var i = 0; i < 2; i++) {
+    if (l is List<A1>) {
+      sum += l[0].str.length;
+    }
+  }
+  blackhole(v);
+  return sum;
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph', '*LICM')
+int testNarrowingThroughIndexedLoadFromFixedArray(List<A> l) {
+  int sum = 0;
+  final v = l[0];
+  for (var i = 0; i < 2; i++) {
+    if (l is List<A1>) {
+      sum += l[0].str.length;
+    }
+  }
+  blackhole(v);
+  return sum;
+}
+
+void main(List<String> args) {
+  shouldPrint = args.contains("shouldPrint");
+
+  // Prevent shaking of BImpl.load and BImpl.loadWithNamed, if these methods
+  // are shaked (because they are not used) that would inhibit polymorphic
+  // inlining at testNarrowingThroughThisCall{,WithNamedParams}
+  BImpl(A1("")).load(H(A1("")));
+  BImpl(A1("")).loadWithNamedParam(h: H(A1("")));
+
+  for (var i = 0; i < 2; i++) {
+    final a1 = A1(i.toString());
+    final a0 = A0(i.toDouble(), i.toString());
+    B1(a1).testNarrowingThroughThisCallWithPositionalParam(H(a1));
+    B0(a0).testNarrowingThroughThisCallWithPositionalParam(H(a0));
+    B1(a1).testNarrowingThroughThisCallWithNamedParams(H(a1));
+    B0(a0).testNarrowingThroughThisCallWithNamedParams(H(a0));
+    testNarrowingThroughIsCheckOnSubclass(B1(a1));
+    testNarrowingThroughIsCheckOnSubclass(B0(a0));
+    testNarrowingThroughIsCheckWithTypeArgPolymorphic(B1(a1));
+    testNarrowingThroughIsCheckWithTypeArgPolymorphic(B0(a0));
+    testNarrowingThroughIsCheckWithTypeArgMonomorphic(BImpl<A1>(a1));
+    testNarrowingThroughIsCheckWithTypeArgMonomorphic(BImpl<A0>(a0));
+    testNarrowingThroughAsCheckWithTypeArgPolymorphic(B1(a1));
+    try {
+      testNarrowingThroughAsCheckWithTypeArgPolymorphic(B0(a0));
+      throw "Should be unreachable";
+    } on TypeError catch (e) {}
+    testNarrowingThroughPhiOfAsChecksWithTypeArgPolymorphic(B1(a1));
+    try {
+      testNarrowingThroughPhiOfAsChecksWithTypeArgPolymorphic(B0(a0));
+      throw "Should be unreachable";
+    } on TypeError catch (e) {}
+    testNarrowingThroughIndexedLoadFromGrowableArray([a1]);
+    testNarrowingThroughIndexedLoadFromGrowableArray([a0]);
+    testNarrowingThroughIndexedLoadFromFixedArray(
+        List<A1>.filled(1, a1, growable: false));
+    testNarrowingThroughIndexedLoadFromFixedArray(
+        List<A0>.filled(1, a0, growable: false));
+  }
+}
+
+void matchIL$testNarrowingThroughThisCallWithPositionalParam(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  final env = beforeLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'this' << match.Parameter(index: 0),
+      'h_raw' << match.Parameter(index: 1),
+      'h' << match.AssertAssignable('h_raw', match.any),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'this_cid' << match.LoadClassId('this'),
+          match.Branch(match.StrictCompare('this_cid', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          match.Redefinition('this'),
+          // This redefinition was inserted by inlining.
+          'h_' << match.Redefinition('h'),
+          'v0' << match.LoadField('h_', slot: 'data'),
+          'v1' << match.LoadField('v0', slot: 'str'),
+          'v2' << match.LoadField('v1', slot: 'String.length'),
+        ]),
+  ]);
+
+  afterLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'this' << match.Parameter(index: 0),
+      'h_raw' << match.Parameter(index: 1),
+      'h' << match.AssertAssignable('h_raw', match.any),
+      'this_cid' << match.LoadClassId('this'), // Hoisted.
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          match.Branch(match.StrictCompare('this_cid', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          // After LICM redefinitions are removed.
+          'v0' << match.LoadField('h', slot: 'data'),
+          'v1' << match.LoadField('v0', slot: 'str'),
+          'v2' << match.LoadField('v1', slot: 'String.length'),
+        ]),
+  ], env: env);
+}
+
+void matchIL$testNarrowingThroughThisCallWithNamedParams(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  // Graph shape is basically the same.
+  matchIL$testNarrowingThroughThisCallWithPositionalParam(
+      beforeLICM, afterLICM);
+}
+
+void matchIL$testNarrowingThroughIsCheckOnSubclass(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  final env = beforeLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'b_cid' << match.LoadClassId('b'),
+          match.Branch(match.StrictCompare('b_cid', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          match.Redefinition('b'),
+          // This redefinition was inserted by load forwarding.
+          'b.v*' << match.Redefinition('b.v'),
+          'v0' << match.LoadField('b.v*', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ]);
+
+  afterLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      'b_cid' << match.LoadClassId('b'), // Hoisted.
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          match.Branch(match.StrictCompare('b_cid', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          'v0' << match.LoadField('b.v', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ], env: env);
+}
+
+void matchIL$testNarrowingThroughIsCheckWithTypeArgMonomorphic(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  final env = beforeLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'b_is_B<A1>' << match.InstanceOf('b', match.any, match.any),
+          match.Branch(
+              match.StrictCompare('b_is_B<A1>', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          match.Redefinition('b'),
+          // This redefinition was inserted by load forwarding.
+          'b.v*' << match.Redefinition('b.v'),
+          'v0' << match.LoadField('b.v*', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ]);
+
+  afterLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'b_is_B<A1>' << match.InstanceOf('b', match.any, match.any),
+          match.Branch(
+              match.StrictCompare('b_is_B<A1>', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          'v0' << match.LoadField('b.v', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ], env: env);
+}
+
+void matchIL$testNarrowingThroughAsCheckWithTypeArgPolymorphic(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  final env = beforeLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          match.Branch(match.EqualityCompare(match.any, match.any, kind: '=='),
+              ifTrue: 'B3', ifFalse: 'B4'),
+        ]),
+    'B3' << match.block('Target', [match.Throw(match.any)]),
+    'B4' <<
+        match.block('Target', [
+          match.AssertAssignable('b', match.any, match.any, match.any),
+          // This redefinition was inserted by load forwarding.
+          'b.v*' << match.Redefinition('b.v'),
+          'v0' << match.LoadField('b.v*', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ]);
+
+  afterLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          match.Branch(match.EqualityCompare(match.any, match.any, kind: '=='),
+              ifTrue: 'B3', ifFalse: 'B4'),
+        ]),
+    'B3' << match.block('Target', [match.Throw(match.any)]),
+    'B4' <<
+        match.block('Target', [
+          match.AssertAssignable('b', match.any, match.any, match.any),
+          'v0' << match.LoadField('b.v', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ], env: env);
+}
+
+void matchIL$testNarrowingThroughPhiOfAsChecksWithTypeArgPolymorphic(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  final env = beforeLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          match.Branch(match.EqualityCompare(match.any, match.any, kind: '=='),
+              ifTrue: 'B3', ifFalse: 'B4'),
+        ]),
+    'B3' << match.block('Target', [match.Throw(match.any)]),
+    'B4' <<
+        match.block('Target', [
+          match.Branch(match.EqualityCompare(match.any, match.any, kind: '=='),
+              ifTrue: 'B5', ifFalse: 'B6'),
+        ]),
+    'B5' <<
+        match.block('Target', [
+          match.AssertAssignable('b', match.any, match.any, match.any),
+          match.Goto('B7'),
+        ]),
+    'B6' <<
+        match.block('Target', [
+          match.AssertAssignable('b', match.any, match.any, match.any),
+          match.Goto('B7'),
+        ]),
+    'B7' <<
+        match.block('Join', [
+          // This redefinition was inserted by PhiInstr::Canonicalize
+          // which removed Phi of two AssertAssignables above.
+          match.Redefinition('b'),
+          // This redefinition was inserted by load forwarding.
+          'b.v*' << match.Redefinition('b.v'),
+          'v0' << match.LoadField('b.v*', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ]);
+
+  afterLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          match.Branch(match.EqualityCompare(match.any, match.any, kind: '=='),
+              ifTrue: 'B3', ifFalse: 'B4'),
+        ]),
+    'B3' << match.block('Target', [match.Throw(match.any)]),
+    'B4' <<
+        match.block('Target', [
+          match.Branch(match.EqualityCompare(match.any, match.any, kind: '=='),
+              ifTrue: 'B5', ifFalse: 'B6'),
+        ]),
+    'B5' <<
+        match.block('Target', [
+          match.AssertAssignable('b', match.any, match.any, match.any),
+          match.Goto('B7'),
+        ]),
+    'B6' <<
+        match.block('Target', [
+          match.AssertAssignable('b', match.any, match.any, match.any),
+          match.Goto('B7'),
+        ]),
+    'B7' <<
+        match.block('Join', [
+          'v0' << match.LoadField('b.v', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ], env: env);
+}
+
+void matchIL$testNarrowingThroughIndexedLoadFromGrowableArray(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  final env = beforeLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'this' << match.Parameter(index: 0),
+      'a.data' << match.LoadField('this', slot: 'GrowableObjectArray.data'),
+      'a.data[0]' << match.LoadIndexed('a.data', match.any),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          'a.data[0]*' << match.Phi('a.data[0]', match.any),
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'this_is_List' << match.InstanceOf('this', match.any, match.any),
+          match.Branch(
+              match.StrictCompare('this_is_List', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          match.Redefinition('this'),
+          // This redefinition was inserted by load forwarding.
+          'a.data[0]**' << match.Redefinition('a.data[0]*'),
+          'a.data[0].str' << match.LoadField('a.data[0]**', slot: 'str'),
+        ]),
+  ]);
+
+  afterLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'this' << match.Parameter(index: 0),
+      'a.data' << match.LoadField('this', slot: 'GrowableObjectArray.data'),
+      'a.data[0]' << match.LoadIndexed('a.data', match.any),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          'a.data[0]*' << match.Phi('a.data[0]', match.any),
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'this_is_List' << match.InstanceOf('this', match.any, match.any),
+          match.Branch(
+              match.StrictCompare('this_is_List', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          'a.data[0].str' << match.LoadField('a.data[0]*', slot: 'str'),
+        ]),
+  ], env: env);
+}
+
+void matchIL$testNarrowingThroughIsCheckWithTypeArgPolymorphic(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  matchIL$testNarrowingThroughIsCheckWithTypeArgMonomorphic(
+      beforeLICM, afterLICM);
+}
+
+void matchIL$testNarrowingThroughIndexedLoadFromFixedArray(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  final env = beforeLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'this' << match.Parameter(index: 0),
+      'a[0]' << match.LoadIndexed('this', match.any),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          'a[0]*' << match.Phi('a[0]', match.any),
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'this_is_List' << match.InstanceOf('this', match.any, match.any),
+          match.Branch(
+              match.StrictCompare('this_is_List', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          match.Redefinition('this'),
+          // This redefinition was inserted by load forwarding.
+          'a[0]**' << match.Redefinition('a[0]*'),
+          'a[0].str' << match.LoadField('a[0]**', slot: 'str'),
+        ]),
+  ]);
+
+  afterLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'this' << match.Parameter(index: 0),
+      'a[0]' << match.LoadIndexed('this', match.any),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          'a[0]*' << match.Phi('a[0]', match.any),
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'this_is_List' << match.InstanceOf('this', match.any, match.any),
+          match.Branch(
+              match.StrictCompare('this_is_List', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          'a[0].str' << match.LoadField('a[0]*', slot: 'str'),
+        ]),
+  ], env: env);
+}
diff --git a/runtime/tests/vm/dart_2/aot_prefer_equality_comparison_il_test.dart b/runtime/tests/vm/dart_2/aot_prefer_equality_comparison_il_test.dart
index 9baf000..8447554 100644
--- a/runtime/tests/vm/dart_2/aot_prefer_equality_comparison_il_test.dart
+++ b/runtime/tests/vm/dart_2/aot_prefer_equality_comparison_il_test.dart
@@ -5,14 +5,21 @@
 // Test that we emit EqualityCompare rather than StrictCompare+BoxInt64
 // when comparing non-nullable integer to a Smi.
 
-// MatchIL[AOT]=factorial
-// __ GraphEntry
-// __ FunctionEntry
-// __ CheckStackOverflow
-// __ Branch(EqualityCompare)
+import 'package:vm/testing/il_matchers.dart';
+
 @pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph')
 int factorial(int value) => value == 1 ? value : value * factorial(value - 1);
 
+void matchIL$factorial(FlowGraph graph) {
+  graph.match([
+    match.block('Graph'),
+    match.block('Function', [
+      match.Branch(match.EqualityCompare(match.any, match.any, kind: '==')),
+    ]),
+  ]);
+}
+
 void main() {
   print(factorial(4));
 }
diff --git a/runtime/tests/vm/dart_2/flutter_regress_91370_il_test.dart b/runtime/tests/vm/dart_2/flutter_regress_91370_il_test.dart
new file mode 100644
index 0000000..722da15
--- /dev/null
+++ b/runtime/tests/vm/dart_2/flutter_regress_91370_il_test.dart
@@ -0,0 +1,718 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:vm/testing/il_matchers.dart';
+
+bool shouldPrint = false;
+
+@pragma('vm:never-inline')
+void blackhole(Object v) {
+  if (shouldPrint) {
+    print(v);
+  }
+}
+
+class A {}
+
+class A0 extends A {
+  final double x;
+  final String str;
+
+  A0(this.x, this.str);
+
+  // Use [x] to prevent the field from being shaken out. We would like
+  // [x] to occupy the same location that [A1.str] takes so that
+  // type confusion / incorrect LICM would cause a crash when we load
+  // `o.[A1.str].length` on an object of type [A0]
+  String toString() => 'A0($x)';
+}
+
+class A1 extends A {
+  final String str;
+
+  A1(this.str);
+}
+
+class H<T> {
+  final T data;
+  H(this.data);
+}
+
+abstract class B<T extends A> {
+  final T v;
+
+  B(this.v);
+
+  int load(H<T> h);
+  int loadWithNamedParam({required H<T> h});
+
+  @pragma('vm:never-inline')
+  @pragma('vm:testing:print-flow-graph', '*LICM')
+  int testNarrowingThroughThisCallWithPositionalParam(H<T> h) {
+    var result = 0;
+    for (var i = 0; i < 10; i++) {
+      // We will perform polymorphic inlining of `load` because `this`
+      // is known to be either B0 or B1. In both cases we will have
+      // v.str.length loads fully inlined because inlined bodies
+      // have precise type information for v.
+      // Then we will hoist v.str.length out of the loop past
+      // class-id comparisons generated by the inlining leading
+      // to incorrect code which will crash.
+      result += load(h);
+    }
+    return result;
+  }
+
+  @pragma('vm:never-inline')
+  @pragma('vm:testing:print-flow-graph', '*LICM')
+  int testNarrowingThroughThisCallWithNamedParams(H<T> h) {
+    var result = 0;
+    for (var i = 0; i < 10; i++) {
+      // We will perform polymorphic inlining of `load` because `this`
+      // is known to be either B0 or B1. In both cases we will have
+      // v.str.length loads fully inlined because inlined bodies
+      // have precise type information for v.
+      // Then we will hoist v.str.length out of the loop past
+      // class-id comparisons generated by the inlining leading
+      // to incorrect code which will crash.
+      result += loadWithNamedParam(h: h);
+    }
+    return result;
+  }
+}
+
+class BImpl<T extends A> extends B<T> {
+  BImpl(T v) : super(v);
+
+  // These methods do not matter. They can just return 0.
+  int load(H<T> h) => 0;
+  int loadWithNamedParam({required H<T> h}) => 0;
+}
+
+class B0 extends B<A0> {
+  B0(A0 a) : super(a);
+
+  int load(H<A0> h) {
+    return h.data.str.length;
+  }
+
+  int loadWithNamedParam({String? a, required H<A0> h, String? z}) {
+    return h.data.str.length;
+  }
+}
+
+class B1 extends B<A1> {
+  B1(A1 a) : super(a);
+
+  int load(H<A1> h) {
+    return h.data.str.length;
+  }
+
+  int loadWithNamedParam({String? a, required H<A1> h, String? z}) {
+    return h.data.str.length;
+  }
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph', '*LICM')
+int testNarrowingThroughIsCheckOnSubclass(B b) {
+  int sum = 0;
+  b.v.toString();
+  for (var i = 0; i < 2; i++) {
+    if (b is B1) {
+      sum += b.v.str.length;
+    }
+  }
+  return sum;
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph', '*LICM')
+int testNarrowingThroughIsCheckWithTypeArgPolymorphic(B b) {
+  int sum = 0;
+  final v = b.v;
+  for (var i = 0; i < 2; i++) {
+    if (b is B<A1>) {
+      sum += b.v.str.length;
+    }
+  }
+  blackhole(v);
+  return sum;
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph', '*LICM')
+int testNarrowingThroughIsCheckWithTypeArgMonomorphic(B b) {
+  int sum = 0;
+  final v = b.v;
+  for (var i = 0; i < 2; i++) {
+    if (b is B<A1>) {
+      sum += b.v.str.length;
+    }
+  }
+  blackhole(v);
+  return sum;
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph', '*LICM')
+int testNarrowingThroughAsCheckWithTypeArgPolymorphic(B b) {
+  int sum = 0;
+  final v = b.v;
+  for (var i = 0; i < 2; i++) {
+    // Branch with a side-effect to avoid b as B<A1> hoisting.
+    if (sum == 42) throw '42';
+    sum += (b as B<A1>).v.str.length;
+  }
+  blackhole(v);
+  return sum;
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph', '*LICM')
+int testNarrowingThroughPhiOfAsChecksWithTypeArgPolymorphic(B b) {
+  int sum = 0;
+  final v = b.v;
+  for (var i = 0; i < 2; i++) {
+    // Branch with a side-effect to avoid b as B<A1> hoisting.
+    if (sum == 42) throw '42';
+    sum += (sum == 22 ? b as B<A1> : b as B<A1>).v.str.length;
+  }
+  blackhole(v);
+  return sum;
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph', '*LICM')
+int testNarrowingThroughIndexedLoadFromGrowableArray(List<A> l) {
+  int sum = 0;
+  final v = l[0];
+  for (var i = 0; i < 2; i++) {
+    if (l is List<A1>) {
+      sum += l[0].str.length;
+    }
+  }
+  blackhole(v);
+  return sum;
+}
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph', '*LICM')
+int testNarrowingThroughIndexedLoadFromFixedArray(List<A> l) {
+  int sum = 0;
+  final v = l[0];
+  for (var i = 0; i < 2; i++) {
+    if (l is List<A1>) {
+      sum += l[0].str.length;
+    }
+  }
+  blackhole(v);
+  return sum;
+}
+
+void main(List<String> args) {
+  shouldPrint = args.contains("shouldPrint");
+
+  // Prevent shaking of BImpl.load and BImpl.loadWithNamed, if these methods
+  // are shaked (because they are not used) that would inhibit polymorphic
+  // inlining at testNarrowingThroughThisCall{,WithNamedParams}
+  BImpl(A1("")).load(H(A1("")));
+  BImpl(A1("")).loadWithNamedParam(h: H(A1("")));
+
+  for (var i = 0; i < 2; i++) {
+    final a1 = A1(i.toString());
+    final a0 = A0(i.toDouble(), i.toString());
+    B1(a1).testNarrowingThroughThisCallWithPositionalParam(H(a1));
+    B0(a0).testNarrowingThroughThisCallWithPositionalParam(H(a0));
+    B1(a1).testNarrowingThroughThisCallWithNamedParams(H(a1));
+    B0(a0).testNarrowingThroughThisCallWithNamedParams(H(a0));
+    testNarrowingThroughIsCheckOnSubclass(B1(a1));
+    testNarrowingThroughIsCheckOnSubclass(B0(a0));
+    testNarrowingThroughIsCheckWithTypeArgPolymorphic(B1(a1));
+    testNarrowingThroughIsCheckWithTypeArgPolymorphic(B0(a0));
+    testNarrowingThroughIsCheckWithTypeArgMonomorphic(BImpl<A1>(a1));
+    testNarrowingThroughIsCheckWithTypeArgMonomorphic(BImpl<A0>(a0));
+    testNarrowingThroughAsCheckWithTypeArgPolymorphic(B1(a1));
+    try {
+      testNarrowingThroughAsCheckWithTypeArgPolymorphic(B0(a0));
+      throw "Should be unreachable";
+    } on TypeError catch (e) {}
+    testNarrowingThroughPhiOfAsChecksWithTypeArgPolymorphic(B1(a1));
+    try {
+      testNarrowingThroughPhiOfAsChecksWithTypeArgPolymorphic(B0(a0));
+      throw "Should be unreachable";
+    } on TypeError catch (e) {}
+    testNarrowingThroughIndexedLoadFromGrowableArray([a1]);
+    testNarrowingThroughIndexedLoadFromGrowableArray([a0]);
+    testNarrowingThroughIndexedLoadFromFixedArray(
+        List<A1>.filled(1, a1, growable: false));
+    testNarrowingThroughIndexedLoadFromFixedArray(
+        List<A0>.filled(1, a0, growable: false));
+  }
+}
+
+void matchIL$testNarrowingThroughThisCallWithPositionalParam(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  final env = beforeLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'this' << match.Parameter(index: 0),
+      'h_raw' << match.Parameter(index: 1),
+      'h' << match.AssertAssignable('h_raw', match.any),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'this_cid' << match.LoadClassId('this'),
+          match.Branch(match.StrictCompare('this_cid', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          match.Redefinition('this'),
+          // This redefinition was inserted by inlining.
+          'h_' << match.Redefinition('h'),
+          'v0' << match.LoadField('h_', slot: 'data'),
+          'v1' << match.LoadField('v0', slot: 'str'),
+          'v2' << match.LoadField('v1', slot: 'String.length'),
+        ]),
+  ]);
+
+  afterLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'this' << match.Parameter(index: 0),
+      'h_raw' << match.Parameter(index: 1),
+      'h' << match.AssertAssignable('h_raw', match.any),
+      'this_cid' << match.LoadClassId('this'), // Hoisted.
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          match.Branch(match.StrictCompare('this_cid', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          // After LICM redefinitions are removed.
+          'v0' << match.LoadField('h', slot: 'data'),
+          'v1' << match.LoadField('v0', slot: 'str'),
+          'v2' << match.LoadField('v1', slot: 'String.length'),
+        ]),
+  ], env: env);
+}
+
+void matchIL$testNarrowingThroughThisCallWithNamedParams(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  // Graph shape is basically the same.
+  matchIL$testNarrowingThroughThisCallWithPositionalParam(
+      beforeLICM, afterLICM);
+}
+
+void matchIL$testNarrowingThroughIsCheckOnSubclass(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  final env = beforeLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'b_cid' << match.LoadClassId('b'),
+          match.Branch(match.StrictCompare('b_cid', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          match.Redefinition('b'),
+          // This redefinition was inserted by load forwarding.
+          'b.v*' << match.Redefinition('b.v'),
+          'v0' << match.LoadField('b.v*', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ]);
+
+  afterLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      'b_cid' << match.LoadClassId('b'), // Hoisted.
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          match.Branch(match.StrictCompare('b_cid', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          'v0' << match.LoadField('b.v', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ], env: env);
+}
+
+void matchIL$testNarrowingThroughIsCheckWithTypeArgMonomorphic(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  final env = beforeLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'b_is_B<A1>' << match.InstanceOf('b', match.any, match.any),
+          match.Branch(
+              match.StrictCompare('b_is_B<A1>', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          match.Redefinition('b'),
+          // This redefinition was inserted by load forwarding.
+          'b.v*' << match.Redefinition('b.v'),
+          'v0' << match.LoadField('b.v*', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ]);
+
+  afterLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'b_is_B<A1>' << match.InstanceOf('b', match.any, match.any),
+          match.Branch(
+              match.StrictCompare('b_is_B<A1>', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          'v0' << match.LoadField('b.v', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ], env: env);
+}
+
+void matchIL$testNarrowingThroughAsCheckWithTypeArgPolymorphic(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  final env = beforeLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          match.Branch(match.EqualityCompare(match.any, match.any, kind: '=='),
+              ifTrue: 'B3', ifFalse: 'B4'),
+        ]),
+    'B3' << match.block('Target', [match.Throw(match.any)]),
+    'B4' <<
+        match.block('Target', [
+          match.AssertAssignable('b', match.any, match.any, match.any),
+          // This redefinition was inserted by load forwarding.
+          'b.v*' << match.Redefinition('b.v'),
+          'v0' << match.LoadField('b.v*', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ]);
+
+  afterLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          match.Branch(match.EqualityCompare(match.any, match.any, kind: '=='),
+              ifTrue: 'B3', ifFalse: 'B4'),
+        ]),
+    'B3' << match.block('Target', [match.Throw(match.any)]),
+    'B4' <<
+        match.block('Target', [
+          match.AssertAssignable('b', match.any, match.any, match.any),
+          'v0' << match.LoadField('b.v', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ], env: env);
+}
+
+void matchIL$testNarrowingThroughPhiOfAsChecksWithTypeArgPolymorphic(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  final env = beforeLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          match.Branch(match.EqualityCompare(match.any, match.any, kind: '=='),
+              ifTrue: 'B3', ifFalse: 'B4'),
+        ]),
+    'B3' << match.block('Target', [match.Throw(match.any)]),
+    'B4' <<
+        match.block('Target', [
+          match.Branch(match.EqualityCompare(match.any, match.any, kind: '=='),
+              ifTrue: 'B5', ifFalse: 'B6'),
+        ]),
+    'B5' <<
+        match.block('Target', [
+          match.AssertAssignable('b', match.any, match.any, match.any),
+          match.Goto('B7'),
+        ]),
+    'B6' <<
+        match.block('Target', [
+          match.AssertAssignable('b', match.any, match.any, match.any),
+          match.Goto('B7'),
+        ]),
+    'B7' <<
+        match.block('Join', [
+          // This redefinition was inserted by PhiInstr::Canonicalize
+          // which removed Phi of two AssertAssignables above.
+          match.Redefinition('b'),
+          // This redefinition was inserted by load forwarding.
+          'b.v*' << match.Redefinition('b.v'),
+          'v0' << match.LoadField('b.v*', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ]);
+
+  afterLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'b' << match.Parameter(index: 0),
+      'b.v' << match.LoadField('b', slot: 'v'),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          match.Branch(match.EqualityCompare(match.any, match.any, kind: '=='),
+              ifTrue: 'B3', ifFalse: 'B4'),
+        ]),
+    'B3' << match.block('Target', [match.Throw(match.any)]),
+    'B4' <<
+        match.block('Target', [
+          match.Branch(match.EqualityCompare(match.any, match.any, kind: '=='),
+              ifTrue: 'B5', ifFalse: 'B6'),
+        ]),
+    'B5' <<
+        match.block('Target', [
+          match.AssertAssignable('b', match.any, match.any, match.any),
+          match.Goto('B7'),
+        ]),
+    'B6' <<
+        match.block('Target', [
+          match.AssertAssignable('b', match.any, match.any, match.any),
+          match.Goto('B7'),
+        ]),
+    'B7' <<
+        match.block('Join', [
+          'v0' << match.LoadField('b.v', slot: 'str'),
+          'v1' << match.LoadField('v0', slot: 'String.length'),
+        ]),
+  ], env: env);
+}
+
+void matchIL$testNarrowingThroughIndexedLoadFromGrowableArray(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  final env = beforeLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'this' << match.Parameter(index: 0),
+      'a.data' << match.LoadField('this', slot: 'GrowableObjectArray.data'),
+      'a.data[0]' << match.LoadIndexed('a.data', match.any),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          'a.data[0]*' << match.Phi('a.data[0]', match.any),
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'this_is_List' << match.InstanceOf('this', match.any, match.any),
+          match.Branch(
+              match.StrictCompare('this_is_List', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          match.Redefinition('this'),
+          // This redefinition was inserted by load forwarding.
+          'a.data[0]**' << match.Redefinition('a.data[0]*'),
+          'a.data[0].str' << match.LoadField('a.data[0]**', slot: 'str'),
+        ]),
+  ]);
+
+  afterLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'this' << match.Parameter(index: 0),
+      'a.data' << match.LoadField('this', slot: 'GrowableObjectArray.data'),
+      'a.data[0]' << match.LoadIndexed('a.data', match.any),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          'a.data[0]*' << match.Phi('a.data[0]', match.any),
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'this_is_List' << match.InstanceOf('this', match.any, match.any),
+          match.Branch(
+              match.StrictCompare('this_is_List', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          'a.data[0].str' << match.LoadField('a.data[0]*', slot: 'str'),
+        ]),
+  ], env: env);
+}
+
+void matchIL$testNarrowingThroughIsCheckWithTypeArgPolymorphic(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  matchIL$testNarrowingThroughIsCheckWithTypeArgMonomorphic(
+      beforeLICM, afterLICM);
+}
+
+void matchIL$testNarrowingThroughIndexedLoadFromFixedArray(
+    FlowGraph beforeLICM, FlowGraph afterLICM) {
+  final env = beforeLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'this' << match.Parameter(index: 0),
+      'a[0]' << match.LoadIndexed('this', match.any),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          'a[0]*' << match.Phi('a[0]', match.any),
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'this_is_List' << match.InstanceOf('this', match.any, match.any),
+          match.Branch(
+              match.StrictCompare('this_is_List', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          match.Redefinition('this'),
+          // This redefinition was inserted by load forwarding.
+          'a[0]**' << match.Redefinition('a[0]*'),
+          'a[0].str' << match.LoadField('a[0]**', slot: 'str'),
+        ]),
+  ]);
+
+  afterLICM.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'this' << match.Parameter(index: 0),
+      'a[0]' << match.LoadIndexed('this', match.any),
+      match.Goto('B1'),
+    ]),
+    'B1' <<
+        match.block('Join', [
+          'a[0]*' << match.Phi('a[0]', match.any),
+          match.CheckStackOverflow(),
+          match.Branch(match.RelationalOp(match.any, match.any, kind: '<'),
+              ifTrue: 'B2'),
+        ]),
+    'B2' <<
+        match.block('Target', [
+          'this_is_List' << match.InstanceOf('this', match.any, match.any),
+          match.Branch(
+              match.StrictCompare('this_is_List', match.any, kind: '==='),
+              ifTrue: 'B3'),
+        ]),
+    'B3' <<
+        match.block('Target', [
+          'a[0].str' << match.LoadField('a[0]*', slot: 'str'),
+        ]),
+  ], env: env);
+}
diff --git a/runtime/vm/compiler/api/print_filter.cc b/runtime/vm/compiler/api/print_filter.cc
index be9762c..4abe1e9 100644
--- a/runtime/vm/compiler/api/print_filter.cc
+++ b/runtime/vm/compiler/api/print_filter.cc
@@ -7,24 +7,46 @@
 #include "vm/compiler/api/print_filter.h"
 
 #include "vm/flags.h"
+#if !defined(DART_PRECOMPILED_RUNTIME)
+#include "vm/compiler/compiler_pass.h"
 #include "vm/object.h"
+#endif
+#include "vm/symbols.h"
 
 namespace dart {
 
 DEFINE_FLAG(charp,
             print_flow_graph_filter,
-            NULL,
+            nullptr,
             "Print only IR of functions with matching names");
 
 namespace compiler {
 
 // Checks whether function's name matches the given filter, which is
 // a comma-separated list of strings.
-static bool PassesFilter(const char* filter, const Function& function) {
-  if (filter == NULL) {
+static bool PassesFilter(const char* filter,
+                         const Function& function,
+                         uint8_t** compiler_pass_filter) {
+  if (filter == nullptr) {
     return true;
   }
 
+#if !defined(DART_PRECOMPILED_RUNTIME)
+  if (strcmp(filter, "@pragma") == 0) {
+    Object& pass_filter = Object::Handle();
+    const auto has_pragma =
+        Library::FindPragma(dart::Thread::Current(), /*only_core=*/false,
+                            function, Symbols::vm_testing_print_flow_graph(),
+                            /*multiple=*/false, &pass_filter);
+    if (has_pragma && !pass_filter.IsNull() &&
+        compiler_pass_filter != nullptr) {
+      *compiler_pass_filter = dart::CompilerPass::ParseFiltersFromPragma(
+          String::Cast(pass_filter).ToCString());
+    }
+    return has_pragma;
+  }
+#endif
+
   char* save_ptr;  // Needed for strtok_r.
   const char* scrubbed_name =
       String::Handle(function.QualifiedScrubbedName()).ToCString();
@@ -36,9 +58,9 @@
   strncpy(filter_buffer, filter, len);  // strtok modifies arg 1.
   char* token = strtok_r(filter_buffer, ",", &save_ptr);
   bool found = false;
-  while (token != NULL) {
-    if ((strstr(function_name, token) != NULL) ||
-        (strstr(scrubbed_name, token) != NULL)) {
+  while (token != nullptr) {
+    if ((strstr(function_name, token) != nullptr) ||
+        (strstr(scrubbed_name, token) != nullptr)) {
       found = true;
       break;
     }
@@ -53,15 +75,17 @@
         }
       }
     }
-    token = strtok_r(NULL, ",", &save_ptr);
+    token = strtok_r(nullptr, ",", &save_ptr);
   }
   delete[] filter_buffer;
 
   return found;
 }
 
-bool PrintFilter::ShouldPrint(const Function& function) {
-  return PassesFilter(FLAG_print_flow_graph_filter, function);
+bool PrintFilter::ShouldPrint(const Function& function,
+                              uint8_t** compiler_pass_filter /* = nullptr */) {
+  return PassesFilter(FLAG_print_flow_graph_filter, function,
+                      compiler_pass_filter);
 }
 
 }  // namespace compiler
diff --git a/runtime/vm/compiler/api/print_filter.h b/runtime/vm/compiler/api/print_filter.h
index 604fa9c..95d7a39 100644
--- a/runtime/vm/compiler/api/print_filter.h
+++ b/runtime/vm/compiler/api/print_filter.h
@@ -19,7 +19,8 @@
 
 class PrintFilter : public AllStatic {
  public:
-  static bool ShouldPrint(const Function& function);
+  static bool ShouldPrint(const Function& function,
+                          uint8_t** compiler_pass_filter = nullptr);
 };
 
 }  // namespace compiler
diff --git a/runtime/vm/compiler/backend/flow_graph.cc b/runtime/vm/compiler/backend/flow_graph.cc
index b0195987..1dbf213 100644
--- a/runtime/vm/compiler/backend/flow_graph.cc
+++ b/runtime/vm/compiler/backend/flow_graph.cc
@@ -56,7 +56,10 @@
       loop_invariant_loads_(nullptr),
       captured_parameters_(new (zone()) BitVector(zone(), variable_count())),
       inlining_id_(-1),
-      should_print_(FlowGraphPrinter::ShouldPrint(parsed_function.function())) {
+      should_print_(false) {
+  should_print_ = FlowGraphPrinter::ShouldPrint(parsed_function.function(),
+                                                &compiler_pass_filters_);
+
   direct_parameters_size_ = ParameterOffsetAt(
       function(), num_direct_parameters_, /*last_slot*/ false);
   DiscoverBlocks();
diff --git a/runtime/vm/compiler/backend/flow_graph.h b/runtime/vm/compiler/backend/flow_graph.h
index c847bab..2c82c13 100644
--- a/runtime/vm/compiler/backend/flow_graph.h
+++ b/runtime/vm/compiler/backend/flow_graph.h
@@ -469,6 +469,9 @@
   void RenameUsesDominatedByRedefinitions();
 
   bool should_print() const { return should_print_; }
+  const uint8_t* compiler_pass_filters() const {
+    return compiler_pass_filters_;
+  }
 
   //
   // High-level utilities.
@@ -632,6 +635,7 @@
 
   intptr_t inlining_id_;
   bool should_print_;
+  uint8_t* compiler_pass_filters_ = nullptr;
 
   const Array* coverage_array_ = &Array::empty_array();
 };
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index 2ba0335..4ca03ed 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -1135,18 +1135,18 @@
 
 // Returns true if the value represents a constant.
 bool Value::BindsToConstant() const {
-  return definition()->IsConstant();
+  return definition()->OriginalDefinition()->IsConstant();
 }
 
 // Returns true if the value represents constant null.
 bool Value::BindsToConstantNull() const {
-  ConstantInstr* constant = definition()->AsConstant();
+  ConstantInstr* constant = definition()->OriginalDefinition()->AsConstant();
   return (constant != NULL) && constant->value().IsNull();
 }
 
 const Object& Value::BoundConstant() const {
   ASSERT(BindsToConstant());
-  ConstantInstr* constant = definition()->AsConstant();
+  ConstantInstr* constant = definition()->OriginalDefinition()->AsConstant();
   ASSERT(constant != NULL);
   return constant->value();
 }
@@ -1207,7 +1207,7 @@
 // ==== Support for visiting flow graphs.
 
 #define DEFINE_ACCEPT(ShortName, Attrs)                                        \
-  void ShortName##Instr::Accept(FlowGraphVisitor* visitor) {                   \
+  void ShortName##Instr::Accept(InstructionVisitor* visitor) {                 \
     visitor->Visit##ShortName(this);                                           \
   }
 
@@ -2545,7 +2545,8 @@
   if (!HasUses() && !flow_graph->is_licm_allowed()) {
     return NULL;
   }
-  if ((constrained_type() != nullptr) && Type()->IsEqualTo(value()->Type())) {
+  if (constrained_type() != nullptr &&
+      constrained_type()->IsEqualTo(value()->Type())) {
     return value()->definition();
   }
   return this;
@@ -5919,8 +5920,43 @@
   }
 }
 
+static bool AllInputsAreRedefinitions(PhiInstr* phi) {
+  for (intptr_t i = 0; i < phi->InputCount(); i++) {
+    if (phi->InputAt(i)->definition()->RedefinedValue() == nullptr) {
+      return false;
+    }
+  }
+  return true;
+}
+
 Definition* PhiInstr::Canonicalize(FlowGraph* flow_graph) {
   Definition* replacement = GetReplacementForRedundantPhi();
+  if (replacement != nullptr && flow_graph->is_licm_allowed() &&
+      AllInputsAreRedefinitions(this)) {
+    // If we are replacing a Phi which has redefinitions as all of its inputs
+    // then to maintain the redefinition chain we are going to insert a
+    // redefinition. If any input is *not* a redefinition that means that
+    // whatever properties were infered for a Phi also hold on a path
+    // that does not pass through any redefinitions so there is no need
+    // to redefine this value.
+    auto zone = flow_graph->zone();
+    auto redef = new (zone) RedefinitionInstr(new (zone) Value(replacement));
+    flow_graph->InsertAfter(block(), redef, /*env=*/nullptr, FlowGraph::kValue);
+
+    // Redefinition is not going to dominate the block entry itself, so we
+    // have to handle environment uses at the block entry specially.
+    Value* next_use;
+    for (Value* use = env_use_list(); use != nullptr; use = next_use) {
+      next_use = use->next_use();
+      if (use->instruction() == block()) {
+        use->RemoveFromUseList();
+        use->set_definition(replacement);
+        replacement->AddEnvUse(use);
+      }
+    }
+    return redef;
+  }
+
   return (replacement != nullptr) ? replacement : this;
 }
 
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index 62c5fb4..4e7efb6 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -11,6 +11,7 @@
 #endif  // defined(DART_PRECOMPILED_RUNTIME)
 
 #include <memory>
+#include <tuple>
 #include <utility>
 
 #include "vm/allocation.h"
@@ -54,6 +55,7 @@
 class FlowGraphVisitor;
 class ForwardInstructionIterator;
 class Instruction;
+class InstructionVisitor;
 class LocalVariable;
 class LoopInfo;
 class ParsedFunction;
@@ -537,7 +539,7 @@
 // Functions required in all concrete instruction classes.
 #define DECLARE_INSTRUCTION_NO_BACKEND(type)                                   \
   virtual Tag tag() const { return k##type; }                                  \
-  virtual void Accept(FlowGraphVisitor* visitor);                              \
+  virtual void Accept(InstructionVisitor* visitor);                            \
   DEFINE_INSTRUCTION_TYPE_CHECK(type)
 
 #define DECLARE_INSTRUCTION_BACKEND()                                          \
@@ -564,9 +566,13 @@
 #define PRINT_TO_SUPPORT virtual void PrintTo(BaseTextBuffer* f) const;
 #define PRINT_OPERANDS_TO_SUPPORT                                              \
   virtual void PrintOperandsTo(BaseTextBuffer* f) const;
+#define DECLARE_ATTRIBUTES(...)                                                \
+  auto GetAttributes() const { return std::make_tuple(__VA_ARGS__); }          \
+  static auto GetAttributeNames() { return std::make_tuple(#__VA_ARGS__); }
 #else
 #define PRINT_TO_SUPPORT
 #define PRINT_OPERANDS_TO_SUPPORT
+#define DECLARE_ATTRIBUTES(...)
 #endif  // defined(INCLUDE_IL_PRINTER)
 
 // Together with CidRange, this represents a mapping from a range of class-ids
@@ -867,7 +873,7 @@
   }
 
   // Visiting support.
-  virtual void Accept(FlowGraphVisitor* visitor) = 0;
+  virtual void Accept(InstructionVisitor* visitor) = 0;
 
   Instruction* previous() const { return previous_; }
   void set_previous(Instruction* instr) {
@@ -2566,6 +2572,7 @@
         block_(block) {}
 
   DECLARE_INSTRUCTION(Parameter)
+  DECLARE_ATTRIBUTES(index())
 
   intptr_t index() const { return index_; }
   intptr_t param_offset() const { return param_offset_; }
@@ -3254,6 +3261,7 @@
 
   virtual TokenPosition token_pos() const { return token_pos_; }
   Token::Kind kind() const { return kind_; }
+  DECLARE_ATTRIBUTES(kind())
 
   virtual ComparisonInstr* CopyWithNewOperands(Value* left, Value* right) = 0;
 
@@ -3762,6 +3770,8 @@
 
   virtual Value* RedefinedValue() const;
 
+  virtual void InferRange(RangeAnalysis* analysis, Range* range);
+
   PRINT_OPERANDS_TO_SUPPORT
 
  private:
@@ -4098,6 +4108,9 @@
   Code::EntryKind entry_kind() const { return entry_kind_; }
   void set_entry_kind(Code::EntryKind value) { entry_kind_ = value; }
 
+  void mark_as_call_on_this() { is_call_on_this_ = true; }
+  bool is_call_on_this() const { return is_call_on_this_; }
+
   DEFINE_INSTRUCTION_TYPE_CHECK(InstanceCallBase);
 
   bool receiver_is_not_smi() const { return receiver_is_not_smi_; }
@@ -4144,6 +4157,7 @@
   bool has_unique_selector_;
   Code::EntryKind entry_kind_ = Code::EntryKind::kNormal;
   bool receiver_is_not_smi_ = false;
+  bool is_call_on_this_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(InstanceCallBaseInstr);
 };
@@ -4254,6 +4268,9 @@
     new_call->set_result_type(call->result_type());
     new_call->set_entry_kind(call->entry_kind());
     new_call->set_has_unique_selector(call->has_unique_selector());
+    if (call->is_call_on_this()) {
+      new_call->mark_as_call_on_this();
+    }
     return new_call;
   }
 
@@ -6674,6 +6691,8 @@
   bool IsPotentialUnboxedDartFieldLoad() const;
 
   DECLARE_INSTRUCTION(LoadField)
+  DECLARE_ATTRIBUTES(&slot())
+
   virtual CompileType ComputeType() const;
 
   virtual intptr_t DeoptimizationTarget() const { return GetDeoptId(); }
@@ -9664,9 +9683,27 @@
   DISALLOW_COPY_AND_ASSIGN(Environment);
 };
 
+class InstructionVisitor : public ValueObject {
+ public:
+  InstructionVisitor() {}
+  virtual ~InstructionVisitor() {}
+
+// Visit functions for instruction classes, with an empty default
+// implementation.
+#define DECLARE_VISIT_INSTRUCTION(ShortName, Attrs)                            \
+  virtual void Visit##ShortName(ShortName##Instr* instr) {}
+
+  FOR_EACH_INSTRUCTION(DECLARE_VISIT_INSTRUCTION)
+
+#undef DECLARE_VISIT_INSTRUCTION
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(InstructionVisitor);
+};
+
 // Visitor base class to visit each instruction and computation in a flow
 // graph as defined by a reversed list of basic blocks.
-class FlowGraphVisitor : public ValueObject {
+class FlowGraphVisitor : public InstructionVisitor {
  public:
   explicit FlowGraphVisitor(const GrowableArray<BlockEntryInstr*>& block_order)
       : current_iterator_(NULL), block_order_(&block_order) {}
@@ -9680,15 +9717,6 @@
   // instructions in order from the block entry to exit.
   virtual void VisitBlocks();
 
-// Visit functions for instruction classes, with an empty default
-// implementation.
-#define DECLARE_VISIT_INSTRUCTION(ShortName, Attrs)                            \
-  virtual void Visit##ShortName(ShortName##Instr* instr) {}
-
-  FOR_EACH_INSTRUCTION(DECLARE_VISIT_INSTRUCTION)
-
-#undef DECLARE_VISIT_INSTRUCTION
-
  protected:
   void set_block_order(const GrowableArray<BlockEntryInstr*>& block_order) {
     block_order_ = &block_order;
diff --git a/runtime/vm/compiler/backend/il_printer.cc b/runtime/vm/compiler/backend/il_printer.cc
index 1c8b3f8..df13a3a 100644
--- a/runtime/vm/compiler/backend/il_printer.cc
+++ b/runtime/vm/compiler/backend/il_printer.cc
@@ -4,6 +4,8 @@
 
 #include "vm/compiler/backend/il_printer.h"
 
+#include <tuple>
+
 #include "vm/compiler/api/print_filter.h"
 #include "vm/compiler/backend/il.h"
 #include "vm/compiler/backend/linearscan.h"
@@ -20,19 +22,195 @@
             false,
             "Calls display a unary, sorted-by count form of ICData");
 DEFINE_FLAG(bool, print_environments, false, "Print SSA environments.");
+DEFINE_FLAG(bool,
+            print_flow_graph_as_json,
+            false,
+            "Use machine readable output when printing IL graphs.");
 
 DECLARE_FLAG(bool, trace_inlining_intervals);
 
-bool FlowGraphPrinter::ShouldPrint(const Function& function) {
-  return compiler::PrintFilter::ShouldPrint(function);
+class IlTestPrinter : public AllStatic {
+ public:
+  static void PrintGraph(const char* phase, FlowGraph* flow_graph) {
+    JSONWriter writer;
+    writer.OpenObject();
+    writer.PrintProperty("p", phase);
+    writer.PrintProperty("f", flow_graph->function().ToFullyQualifiedCString());
+    writer.OpenArray("b");
+    for (auto block : flow_graph->reverse_postorder()) {
+      PrintBlock(&writer, block);
+    }
+    writer.CloseArray();
+    writer.OpenObject("desc");
+    AttributesSerializer(&writer).WriteDescriptors();
+    writer.CloseObject();
+    writer.CloseObject();
+    THR_Print("%s\n", writer.ToCString());
+  }
+
+  static void PrintBlock(JSONWriter* writer, BlockEntryInstr* block) {
+    writer->OpenObject();
+    writer->PrintProperty64("b", block->block_id());
+    writer->PrintProperty("o", block->DebugName());
+    if (auto block_with_defs = block->AsBlockEntryWithInitialDefs()) {
+      if (block_with_defs->initial_definitions() != nullptr &&
+          block_with_defs->initial_definitions()->length() > 0) {
+        writer->OpenArray("d");
+        for (auto defn : *block_with_defs->initial_definitions()) {
+          if (defn->IsConstant() && !defn->HasUses()) continue;
+          PrintInstruction(writer, defn);
+        }
+        writer->CloseArray();
+      }
+    }
+    writer->OpenArray("is");
+    if (auto join = block->AsJoinEntry()) {
+      for (PhiIterator it(join); !it.Done(); it.Advance()) {
+        PrintInstruction(writer, it.Current());
+      }
+    }
+    for (auto instr : block->instructions()) {
+      PrintInstruction(writer, instr);
+    }
+    writer->CloseArray();
+    writer->CloseObject();
+  }
+
+  static void PrintInstruction(JSONWriter* writer,
+                               Instruction* instr,
+                               const char* name = nullptr) {
+    writer->OpenObject(name);
+    if (auto defn = instr->AsDefinition()) {
+      if (defn->ssa_temp_index() != -1) {
+        writer->PrintProperty("v", defn->ssa_temp_index());
+      }
+    }
+    writer->PrintProperty("o", instr->DebugName());
+    if (auto branch = instr->AsBranch()) {
+      PrintInstruction(writer, branch->comparison(), "cc");
+    } else {
+      if (instr->InputCount() != 0) {
+        writer->OpenArray("i");
+        for (intptr_t i = 0; i < instr->InputCount(); i++) {
+          writer->PrintValue(instr->InputAt(i)->definition()->ssa_temp_index());
+        }
+        writer->CloseArray();
+      } else if (instr->ArgumentCount() != 0 &&
+                 instr->GetPushArguments() != nullptr) {
+        writer->OpenArray("i");
+        for (intptr_t i = 0; i < instr->ArgumentCount(); i++) {
+          writer->PrintValue(
+              instr->ArgumentValueAt(i)->definition()->ssa_temp_index());
+        }
+        writer->CloseArray();
+      }
+      AttributesSerializer serializer(writer);
+      instr->Accept(&serializer);
+    }
+    if (instr->SuccessorCount() > 0) {
+      writer->OpenArray("s");
+      for (auto succ : instr->successors()) {
+        writer->PrintValue(succ->block_id());
+      }
+      writer->CloseArray();
+    }
+    writer->CloseObject();
+  }
+
+  template <typename T>
+  class HasGetAttributes {
+    template <typename U>
+    static std::true_type test(decltype(&U::GetAttributes));
+    template <typename U>
+    static std::false_type test(...);
+
+   public:
+    static constexpr bool value = decltype(test<T>(0))::value;
+  };
+
+  class AttributesSerializer : public InstructionVisitor {
+   public:
+    explicit AttributesSerializer(JSONWriter* writer) : writer_(writer) {}
+
+    void WriteDescriptors() {
+#define DECLARE_VISIT_INSTRUCTION(ShortName, Attrs)                            \
+  WriteDescriptor<ShortName##Instr>(#ShortName);
+
+      FOR_EACH_INSTRUCTION(DECLARE_VISIT_INSTRUCTION)
+
+#undef DECLARE_VISIT_INSTRUCTION
+    }
+
+#define DECLARE_VISIT_INSTRUCTION(ShortName, Attrs)                            \
+  virtual void Visit##ShortName(ShortName##Instr* instr) { Write(instr); }
+
+    FOR_EACH_INSTRUCTION(DECLARE_VISIT_INSTRUCTION)
+
+#undef DECLARE_VISIT_INSTRUCTION
+
+   private:
+    void WriteAttribute(const char* value) { writer_->PrintValue(value); }
+
+    void WriteAttribute(intptr_t value) { writer_->PrintValue(value); }
+
+    void WriteAttribute(Token::Kind kind) {
+      writer_->PrintValue(Token::Str(kind));
+    }
+
+    void WriteAttribute(const Slot* slot) { writer_->PrintValue(slot->Name()); }
+
+    template <typename... Ts>
+    void WriteTuple(const std::tuple<Ts...>& tuple) {
+      std::apply([&](Ts const&... elements) { WriteAttribute(elements...); },
+                 tuple);
+    }
+
+    template <typename T,
+              typename = typename std::enable_if_t<HasGetAttributes<T>::value>>
+    void Write(T* instr) {
+      writer_->OpenArray("d");
+      WriteTuple(instr->GetAttributes());
+      writer_->CloseArray();
+    }
+
+    void Write(Instruction* instr) {
+      // Default, do nothing.
+    }
+
+    template <typename T>
+    void WriteDescriptor(
+        const char* name,
+        typename std::enable_if_t<HasGetAttributes<T>::value>* = 0) {
+      writer_->OpenArray(name);
+      WriteTuple(T::GetAttributeNames());
+      writer_->CloseArray();
+    }
+
+    template <typename T>
+    void WriteDescriptor(
+        const char* name,
+        typename std::enable_if_t<!HasGetAttributes<T>::value>* = 0) {}
+
+    JSONWriter* writer_;
+  };
+};
+
+bool FlowGraphPrinter::ShouldPrint(
+    const Function& function,
+    uint8_t** compiler_pass_filter /* = nullptr */) {
+  return compiler::PrintFilter::ShouldPrint(function, compiler_pass_filter);
 }
 
 void FlowGraphPrinter::PrintGraph(const char* phase, FlowGraph* flow_graph) {
   LogBlock lb;
-  THR_Print("*** BEGIN CFG\n%s\n", phase);
-  FlowGraphPrinter printer(*flow_graph);
-  printer.PrintBlocks();
-  THR_Print("*** END CFG\n");
+  if (FLAG_print_flow_graph_as_json) {
+    IlTestPrinter::PrintGraph(phase, flow_graph);
+  } else {
+    THR_Print("*** BEGIN CFG\n%s\n", phase);
+    FlowGraphPrinter printer(*flow_graph);
+    printer.PrintBlocks();
+    THR_Print("*** END CFG\n");
+  }
   fflush(stdout);
 }
 
@@ -1286,7 +1464,9 @@
   UNREACHABLE();
 }
 
-bool FlowGraphPrinter::ShouldPrint(const Function& function) {
+bool FlowGraphPrinter::ShouldPrint(
+    const Function& function,
+    uint8_t** compiler_pass_filter /* = nullptr */) {
   return false;
 }
 
diff --git a/runtime/vm/compiler/backend/il_printer.h b/runtime/vm/compiler/backend/il_printer.h
index 96268c8..3784083 100644
--- a/runtime/vm/compiler/backend/il_printer.h
+++ b/runtime/vm/compiler/backend/il_printer.h
@@ -56,7 +56,8 @@
   static void PrintCidRangeData(const CallTargets& ic_data,
                                 intptr_t num_checks_to_print = kPrintAll);
 
-  static bool ShouldPrint(const Function& function);
+  static bool ShouldPrint(const Function& function,
+                          uint8_t** compiler_pass_filter = nullptr);
 
  private:
   const Function& function_;
diff --git a/runtime/vm/compiler/backend/inliner.cc b/runtime/vm/compiler/backend/inliner.cc
index 2523166..38de868 100644
--- a/runtime/vm/compiler/backend/inliner.cc
+++ b/runtime/vm/compiler/backend/inliner.cc
@@ -629,18 +629,128 @@
   const Function& caller_function_;
 };
 
+static bool IsAThisCallThroughAnUncheckedEntryPoint(Definition* call) {
+  if (auto instance_call = call->AsInstanceCallBase()) {
+    return (instance_call->entry_kind() == Code::EntryKind::kUnchecked) &&
+           instance_call->is_call_on_this();
+  }
+  return false;
+}
+
+// Helper which returns true if callee potentially has a more specific
+// parameter type and thus a redefinition needs to be inserted.
+static bool CalleeParameterTypeMightBeMoreSpecific(
+    BitVector* is_generic_covariant_impl,
+    const FunctionType& interface_target_signature,
+    const FunctionType& callee_signature,
+    intptr_t first_arg_index,
+    intptr_t arg_index) {
+  if (arg_index > first_arg_index && is_generic_covariant_impl != nullptr &&
+      is_generic_covariant_impl->Contains(arg_index - first_arg_index)) {
+    const intptr_t param_index = arg_index - first_arg_index;
+    const intptr_t num_named_params =
+        callee_signature.NumOptionalNamedParameters();
+    const intptr_t num_params = callee_signature.NumParameters();
+    if (num_named_params == 0 &&
+        param_index >= interface_target_signature.NumParameters()) {
+      // An optional positional parameter which was added in the callee but
+      // not present in the interface target.
+      return false;
+    }
+
+    // Check if this argument corresponds to a named parameter. In this case
+    // we need to find correct index based on the name.
+    intptr_t interface_target_param_index = param_index;
+    if (num_named_params > 0 &&
+        (num_params - num_named_params) <= param_index) {
+      // This is a named parameter.
+      const String& name =
+          String::Handle(callee_signature.ParameterNameAt(param_index));
+      interface_target_param_index = -1;
+      for (intptr_t i = interface_target_signature.NumParameters() -
+                        interface_target_signature.NumOptionalNamedParameters(),
+                    n = interface_target_signature.NumParameters();
+           i < n; i++) {
+        if (interface_target_signature.ParameterNameAt(i) == name.ptr()) {
+          interface_target_param_index = i;
+          break;
+        }
+      }
+
+      // This is a named parameter which was added in the callee.
+      if (interface_target_param_index == -1) {
+        return false;
+      }
+    }
+    const AbstractType& callee_parameter_type =
+        AbstractType::Handle(callee_signature.ParameterTypeAt(param_index));
+    const AbstractType& interface_target_parameter_type =
+        AbstractType::Handle(interface_target_signature.ParameterTypeAt(
+            interface_target_param_index));
+    if (interface_target_parameter_type.ptr() != callee_parameter_type.ptr()) {
+      // This a conservative approximation.
+      return true;
+    }
+  }
+  return false;
+}
+
 static void ReplaceParameterStubs(Zone* zone,
                                   FlowGraph* caller_graph,
                                   InlinedCallData* call_data,
                                   const TargetInfo* target_info) {
   const bool is_polymorphic = call_data->call->IsPolymorphicInstanceCall();
+  const bool no_checks =
+      IsAThisCallThroughAnUncheckedEntryPoint(call_data->call);
   ASSERT(is_polymorphic == (target_info != NULL));
   FlowGraph* callee_graph = call_data->callee_graph;
   auto callee_entry = callee_graph->graph_entry()->normal_entry();
+  const Function& callee = callee_graph->function();
+
+  FunctionType& interface_target_signature = FunctionType::Handle();
+  FunctionType& callee_signature = FunctionType::Handle(callee.signature());
+
+  // If we are inlining a call on this and we are going to skip parameter checks
+  // then a situation can arise when parameter type in the callee has a narrower
+  // type than what interface target specifies, e.g.
+  //
+  //    class A<T> {
+  //      void f(T v);
+  //      void g(T v) { f(v); }
+  //    }
+  //    class B extends A<X> { void f(X v) { ... } }
+  //
+  // Conside when B.f is inlined into a callsite in A.g (e.g. due to polymorphic
+  // inlining). v is known to be X within the body of B.f, but not guaranteed to
+  // be X outside of it. Thus we must ensure that all operations with v that
+  // depend on its type being X are pinned to stay within the inlined body.
+  //
+  // We achieve that by inserting redefinitions for parameters which potentially
+  // have narrower types in callee compared to those in the interface target of
+  // the call.
+  BitVector* is_generic_covariant_impl = nullptr;
+  if (no_checks && callee.IsRegularFunction()) {
+    const Function& interface_target =
+        call_data->call->AsInstanceCallBase()->interface_target();
+
+    callee_signature = callee.signature();
+    interface_target_signature = interface_target.signature();
+
+    // If signatures match then there is nothing to do.
+    if (interface_target.signature() != callee.signature()) {
+      const intptr_t num_params = callee.NumParameters();
+      BitVector is_covariant(zone, num_params);
+      is_generic_covariant_impl = new (zone) BitVector(zone, num_params);
+
+      kernel::ReadParameterCovariance(callee_graph->function(), &is_covariant,
+                                      is_generic_covariant_impl);
+    }
+  }
 
   // Replace each stub with the actual argument or the caller's constant.
   // Nulls denote optional parameters for which no actual was given.
   const intptr_t first_arg_index = call_data->first_arg_index;
+
   // When first_arg_index > 0, the stub and actual argument processed in the
   // first loop iteration represent a passed-in type argument vector.
   GrowableArray<Value*>* arguments = call_data->arguments;
@@ -654,17 +764,29 @@
   }
   for (intptr_t i = 0; i < arguments->length(); ++i) {
     Value* actual = (*arguments)[i];
-    Definition* defn = NULL;
-    if (is_polymorphic && (i == first_arg_index)) {
-      // Replace the receiver argument with a redefinition to prevent code from
-      // the inlined body from being hoisted above the inlined entry.
+    Definition* defn = nullptr;
+
+    // Replace the receiver argument with a redefinition to prevent code from
+    // the inlined body from being hoisted above the inlined entry.
+    const bool is_polymorphic_receiver =
+        (is_polymorphic && (i == first_arg_index));
+
+    if (actual == nullptr) {
+      ASSERT(!is_polymorphic_receiver);
+      continue;
+    }
+
+    if (is_polymorphic_receiver ||
+        CalleeParameterTypeMightBeMoreSpecific(
+            is_generic_covariant_impl, interface_target_signature,
+            callee_signature, first_arg_index, i)) {
       RedefinitionInstr* redefinition =
           new (zone) RedefinitionInstr(actual->Copy(zone));
       redefinition->set_ssa_temp_index(caller_graph->alloc_ssa_temp_index());
       if (FlowGraph::NeedsPairLocation(redefinition->representation())) {
         caller_graph->alloc_ssa_temp_index();
       }
-      if (target_info->IsSingleCid()) {
+      if (is_polymorphic_receiver && target_info->IsSingleCid()) {
         redefinition->UpdateType(CompileType::FromCid(target_info->cid_start));
       }
       redefinition->InsertAfter(callee_entry);
@@ -674,13 +796,12 @@
       callee_entry->ReplaceInEnvironment(
           call_data->parameter_stubs->At(first_arg_stub_index + i),
           actual->definition());
-    } else if (actual != NULL) {
+    } else {
       defn = actual->definition();
     }
-    if (defn != NULL) {
-      call_data->parameter_stubs->At(first_arg_stub_index + i)
-          ->ReplaceUsesWith(defn);
-    }
+
+    call_data->parameter_stubs->At(first_arg_stub_index + i)
+        ->ReplaceUsesWith(defn);
   }
 
   // Replace remaining constants with uses by constants in the caller's
@@ -688,7 +809,7 @@
   auto defns = callee_graph->graph_entry()->initial_definitions();
   for (intptr_t i = 0; i < defns->length(); ++i) {
     ConstantInstr* constant = (*defns)[i]->AsConstant();
-    if (constant != NULL && constant->HasUses()) {
+    if (constant != nullptr && constant->HasUses()) {
       constant->ReplaceUsesWith(caller_graph->GetConstant(constant->value()));
     }
   }
@@ -696,12 +817,12 @@
   defns = callee_graph->graph_entry()->normal_entry()->initial_definitions();
   for (intptr_t i = 0; i < defns->length(); ++i) {
     ConstantInstr* constant = (*defns)[i]->AsConstant();
-    if (constant != NULL && constant->HasUses()) {
+    if (constant != nullptr && constant->HasUses()) {
       constant->ReplaceUsesWith(caller_graph->GetConstant(constant->value()));
     }
 
     SpecialParameterInstr* param = (*defns)[i]->AsSpecialParameter();
-    if (param != NULL && param->HasUses()) {
+    if (param != nullptr && param->HasUses()) {
       switch (param->kind()) {
         case SpecialParameterInstr::kContext: {
           ASSERT(!is_polymorphic);
@@ -1422,9 +1543,8 @@
     // Plug result in the caller graph.
     InlineExitCollector* exit_collector = call_data->exit_collector;
     exit_collector->PrepareGraphs(callee_graph);
-    exit_collector->ReplaceCall(callee_function_entry);
-
     ReplaceParameterStubs(zone(), caller_graph_, call_data, NULL);
+    exit_collector->ReplaceCall(callee_function_entry);
 
     ASSERT(!call_data->call->HasPushArguments());
   }
diff --git a/runtime/vm/compiler/backend/inliner_test.cc b/runtime/vm/compiler/backend/inliner_test.cc
index f5c59aa..4760a70 100644
--- a/runtime/vm/compiler/backend/inliner_test.cc
+++ b/runtime/vm/compiler/backend/inliner_test.cc
@@ -188,7 +188,8 @@
       {kMatchAndMoveStoreIndexed, &store_instr},
   }));
 
-  RELEASE_ASSERT(unbox_instr->InputAt(0)->definition() == value_param);
+  RELEASE_ASSERT(unbox_instr->InputAt(0)->definition()->OriginalDefinition() ==
+                 value_param);
   RELEASE_ASSERT(store_instr->InputAt(0)->definition() == list_param);
   RELEASE_ASSERT(store_instr->InputAt(2)->definition() == unbox_instr);
   RELEASE_ASSERT(unbox_instr->is_truncating());
diff --git a/runtime/vm/compiler/backend/range_analysis.cc b/runtime/vm/compiler/backend/range_analysis.cc
index 74a7257..7d02f87 100644
--- a/runtime/vm/compiler/backend/range_analysis.cc
+++ b/runtime/vm/compiler/backend/range_analysis.cc
@@ -3091,7 +3091,22 @@
   }
 }
 
+void AssertAssignableInstr::InferRange(RangeAnalysis* analysis, Range* range) {
+  const Range* value_range = value()->definition()->range();
+  if (!Range::IsUnknown(value_range)) {
+    *range = *value_range;
+  } else {
+    *range = Range::Full(RangeBoundary::kRangeBoundaryInt64);
+  }
+}
+
 static bool IsRedundantBasedOnRangeInformation(Value* index, Value* length) {
+  if (index->BindsToSmiConstant() && length->BindsToSmiConstant()) {
+    const auto index_val = index->BoundSmiConstant();
+    const auto length_val = length->BoundSmiConstant();
+    return (0 <= index_val && index_val < length_val);
+  }
+
   // Range of the index is unknown can't decide if the check is redundant.
   Range* index_range = index->definition()->range();
   if (index_range == nullptr) {
diff --git a/runtime/vm/compiler/backend/redundancy_elimination.cc b/runtime/vm/compiler/backend/redundancy_elimination.cc
index 75a83b4..67f8112 100644
--- a/runtime/vm/compiler/backend/redundancy_elimination.cc
+++ b/runtime/vm/compiler/backend/redundancy_elimination.cc
@@ -1921,6 +1921,72 @@
             (array_store->class_id() == kTypedDataFloat32x4ArrayCid));
   }
 
+  static bool AlreadyPinnedByRedefinition(Definition* replacement,
+                                          Definition* redefinition) {
+    Definition* defn = replacement;
+    if (auto load_field = replacement->AsLoadField()) {
+      defn = load_field->instance()->definition();
+    } else if (auto load_indexed = replacement->AsLoadIndexed()) {
+      defn = load_indexed->array()->definition();
+    }
+
+    Value* unwrapped;
+    while ((unwrapped = defn->RedefinedValue()) != nullptr) {
+      if (defn == redefinition) {
+        return true;
+      }
+      defn = unwrapped->definition();
+    }
+
+    return false;
+  }
+
+  Definition* ReplaceLoad(Definition* load, Definition* replacement) {
+    // When replacing a load from a generic field or from an array element
+    // check if instance we are loading from is redefined. If it is then
+    // we need to ensure that replacement is not going to break the
+    // dependency chain.
+    Definition* redef = nullptr;
+    if (auto load_field = load->AsLoadField()) {
+      auto instance = load_field->instance()->definition();
+      if (instance->RedefinedValue() != nullptr) {
+        if ((load_field->slot().kind() ==
+                 Slot::Kind::kGrowableObjectArray_data ||
+             (load_field->slot().IsDartField() &&
+              !AbstractType::Handle(load_field->slot().field().type())
+                   .IsInstantiated()))) {
+          redef = instance;
+        }
+      }
+    } else if (auto load_indexed = load->AsLoadIndexed()) {
+      if (load_indexed->class_id() == kArrayCid ||
+          load_indexed->class_id() == kImmutableArrayCid) {
+        auto instance = load_indexed->array()->definition();
+        if (instance->RedefinedValue() != nullptr) {
+          redef = instance;
+        }
+      }
+    }
+    if (redef != nullptr && !AlreadyPinnedByRedefinition(replacement, redef)) {
+      // Original load had a redefined instance and replacement does not
+      // depend on the same redefinition. Create a redefinition
+      // of the replacement to keep the dependency chain.
+      auto replacement_redefinition =
+          new (zone()) RedefinitionInstr(new (zone()) Value(replacement));
+      if (redef->IsDominatedBy(replacement)) {
+        graph_->InsertAfter(redef, replacement_redefinition, /*env=*/nullptr,
+                            FlowGraph::kValue);
+      } else {
+        graph_->InsertBefore(load, replacement_redefinition, /*env=*/nullptr,
+                             FlowGraph::kValue);
+      }
+      replacement = replacement_redefinition;
+    }
+
+    load->ReplaceUsesWith(replacement);
+    return replacement;
+  }
+
   // Compute sets of loads generated and killed by each block.
   // Additionally compute upwards exposed and generated loads for each block.
   // Exposed loads are those that can be replaced if a corresponding
@@ -2142,7 +2208,7 @@
                       defn->ssa_temp_index(), replacement->ssa_temp_index());
           }
 
-          defn->ReplaceUsesWith(replacement);
+          ReplaceLoad(defn, replacement);
           instr_it.RemoveCurrentFromGraph();
           forwarded_ = true;
           continue;
@@ -2515,7 +2581,7 @@
                       load->ssa_temp_index(), replacement->ssa_temp_index());
           }
 
-          load->ReplaceUsesWith(replacement);
+          replacement = ReplaceLoad(load, replacement);
           load->RemoveFromGraph();
           load->SetReplacement(replacement);
           forwarded_ = true;
diff --git a/runtime/vm/compiler/backend/type_propagator.cc b/runtime/vm/compiler/backend/type_propagator.cc
index 8e110dd..fbae33b 100644
--- a/runtime/vm/compiler/backend/type_propagator.cc
+++ b/runtime/vm/compiler/backend/type_propagator.cc
@@ -366,9 +366,14 @@
 }
 
 void FlowGraphTypePropagator::VisitAssertAssignable(
-    AssertAssignableInstr* instr) {
-  SetTypeOf(instr->value()->definition(),
-            new (zone()) CompileType(instr->ComputeType()));
+    AssertAssignableInstr* check) {
+  auto defn = check->value()->definition();
+  SetTypeOf(defn, new (zone()) CompileType(check->ComputeType()));
+  if (check->ssa_temp_index() == -1) {
+    flow_graph_->AllocateSSAIndexes(check);
+    GrowTypes(check->ssa_temp_index() + 1);
+  }
+  FlowGraph::RenameDominatedUses(defn, check, check);
 }
 
 void FlowGraphTypePropagator::VisitAssertBoolean(AssertBooleanInstr* instr) {
diff --git a/runtime/vm/compiler/compiler_pass.cc b/runtime/vm/compiler/compiler_pass.cc
index 0d91ec1..4bbce2c 100644
--- a/runtime/vm/compiler/compiler_pass.cc
+++ b/runtime/vm/compiler/compiler_pass.cc
@@ -75,8 +75,9 @@
 }
 
 CompilerPass* CompilerPass::passes_[CompilerPass::kNumPasses] = {NULL};
+uint8_t CompilerPass::flags_[CompilerPass::kNumPasses] = {0};
 
-DEFINE_OPTION_HANDLER(CompilerPass::ParseFilters,
+DEFINE_OPTION_HANDLER(CompilerPass::ParseFiltersFromFlag,
                       compiler_passes,
                       "List of comma separated compilation passes flags. "
                       "Use -Name to disable a pass, Name to print IL after it. "
@@ -110,7 +111,18 @@
     "\n"
     "List of compiler passes:\n";
 
-void CompilerPass::ParseFilters(const char* filter) {
+void CompilerPass::ParseFiltersFromFlag(const char* filter) {
+  ParseFilters(filter, flags_);
+}
+
+uint8_t* CompilerPass::ParseFiltersFromPragma(const char* filter) {
+  auto flags =
+      ThreadState::Current()->zone()->Alloc<uint8_t>(CompilerPass::kNumPasses);
+  ParseFilters(filter, flags);
+  return flags;
+}
+
+void CompilerPass::ParseFilters(const char* filter, uint8_t* pass_flags) {
   if (filter == NULL || *filter == 0) {
     return;
   }
@@ -126,11 +138,7 @@
   }
 
   // Clear all flags.
-  for (intptr_t i = 0; i < kNumPasses; i++) {
-    if (passes_[i] != NULL) {
-      passes_[i]->flags_ = 0;
-    }
-  }
+  memset(pass_flags, 0, CompilerPass::kNumPasses);
 
   for (const char *start = filter, *end = filter; *end != 0;
        start = (end + 1)) {
@@ -144,54 +152,58 @@
       continue;
     }
 
-    uint8_t flags = 0;
-    if (*start == '-') {
-      flags = kDisabled;
-    } else if (*start == ']') {
-      flags = kTraceAfter;
-    } else if (*start == '[') {
-      flags = kTraceBefore;
-    } else if (*start == '*') {
-      flags = kTraceBeforeOrAfter;
+    ParseOneFilter(start, end, pass_flags);
+  }
+}
+
+void CompilerPass::ParseOneFilter(const char* start,
+                                  const char* end,
+                                  uint8_t* pass_flags) {
+  uint8_t flags = 0;
+  if (*start == '-') {
+    flags = kDisabled;
+  } else if (*start == ']') {
+    flags = kTraceAfter;
+  } else if (*start == '[') {
+    flags = kTraceBefore;
+  } else if (*start == '*') {
+    flags = kTraceBeforeOrAfter;
+  }
+  if (flags == 0) {
+    flags |= kTraceAfter;
+  } else {
+    start++;  // Skip the modifier
+  }
+
+  size_t suffix = 0;
+  if (end[-1] == '+') {
+    if (start == (end - 1)) {
+      OS::PrintErr("Sticky modifier '+' should follow pass name\n");
+      return;
     }
-    if (flags == 0) {
-      flags |= kTraceAfter;
+    flags |= kSticky;
+    suffix = 1;
+  }
+
+  size_t length = (end - start) - suffix;
+  if (length != 0) {
+    char* pass_name = Utils::StrNDup(start, length);
+    CompilerPass* pass = FindPassByName(pass_name);
+    if (pass != NULL) {
+      pass_flags[pass->id()] |= flags;
     } else {
-      start++;  // Skip the modifier
+      OS::PrintErr("Unknown compiler pass: %s\n", pass_name);
     }
-
-    size_t suffix = 0;
-    if (end[-1] == '+') {
-      if (start == (end - 1)) {
-        OS::PrintErr("Sticky modifier '+' should follow pass name\n");
-        continue;
-      }
-      flags |= kSticky;
-      suffix = 1;
-    }
-
-    size_t length = (end - start) - suffix;
-    if (length != 0) {
-      char* pass_name = Utils::StrNDup(start, length);
-      CompilerPass* pass = FindPassByName(pass_name);
-      if (pass != NULL) {
-        pass->flags_ |= flags;
-      } else {
-        OS::PrintErr("Unknown compiler pass: %s\n", pass_name);
-      }
-      free(pass_name);
-    } else if (flags == kTraceBeforeOrAfter) {
-      for (intptr_t i = 0; i < kNumPasses; i++) {
-        if (passes_[i] != NULL) {
-          passes_[i]->flags_ = kTraceAfter;
-        }
-      }
+    free(pass_name);
+  } else if (flags == kTraceBeforeOrAfter) {
+    for (intptr_t i = 0; i < kNumPasses; i++) {
+      pass_flags[i] = kTraceAfter;
     }
   }
 }
 
 void CompilerPass::Run(CompilerPassState* state) const {
-  if (IsFlagSet(kDisabled)) {
+  if ((flags() & kDisabled) != 0) {
     return;
   }
 
@@ -231,8 +243,11 @@
 void CompilerPass::PrintGraph(CompilerPassState* state,
                               Flag mask,
                               intptr_t round) const {
-  const intptr_t current_flags = flags() | state->sticky_flags;
   FlowGraph* flow_graph = state->flow_graph();
+  const uint8_t* graph_flags = flow_graph->compiler_pass_filters();
+  const uint8_t current_flags =
+      (graph_flags != nullptr ? graph_flags[id()] : flags()) |
+      state->sticky_flags;
 
   if ((FLAG_print_flow_graph || FLAG_print_flow_graph_optimized) &&
       flow_graph->should_print() && ((current_flags & mask) != 0)) {
@@ -558,6 +573,7 @@
   flow_graph->function().set_inlining_depth(state->inlining_depth);
   // Remove redefinitions for the rest of the pipeline.
   flow_graph->RemoveRedefinitions();
+  flow_graph->Canonicalize();
 });
 
 COMPILER_PASS(GenerateCode, { state->graph_compiler->CompileGraph(); });
diff --git a/runtime/vm/compiler/compiler_pass.h b/runtime/vm/compiler/compiler_pass.h
index 9afa960..01a7194 100644
--- a/runtime/vm/compiler/compiler_pass.h
+++ b/runtime/vm/compiler/compiler_pass.h
@@ -115,13 +115,15 @@
   static constexpr intptr_t kNumPasses = 0 COMPILER_PASS_LIST(ADD_ONE);
 #undef ADD_ONE
 
-  CompilerPass(Id id, const char* name) : id_(id), name_(name), flags_(0) {
+  CompilerPass(Id id, const char* name) : id_(id), name_(name) {
     ASSERT(passes_[id] == NULL);
     passes_[id] = this;
 
     // By default print the final flow-graph after the register allocation.
     if (id == kAllocateRegisters) {
-      flags_ = kTraceAfter;
+      flags_[id] = kTraceAfter;
+    } else {
+      flags_[id] = 0;
     }
   }
   virtual ~CompilerPass() {}
@@ -136,15 +138,18 @@
 
   void Run(CompilerPassState* state) const;
 
-  intptr_t flags() const { return flags_; }
+  uint8_t flags() const { return flags_[id()]; }
   const char* name() const { return name_; }
   Id id() const { return id_; }
 
-  bool IsFlagSet(Flag flag) const { return (flags() & flag) != 0; }
-
   static CompilerPass* Get(Id id) { return passes_[id]; }
 
-  static void ParseFilters(const char* filter);
+  static void ParseFiltersFromFlag(const char* filter);
+  static uint8_t* ParseFiltersFromPragma(const char* filter);
+  static void ParseFilters(const char* filter, uint8_t* flags);
+  static void ParseOneFilter(const char* start,
+                             const char* end,
+                             uint8_t* flags);
 
   enum PipelineMode { kJIT, kAOT };
 
@@ -196,10 +201,10 @@
   void PrintGraph(CompilerPassState* state, Flag mask, intptr_t round) const;
 
   static CompilerPass* passes_[];
+  static uint8_t flags_[];
 
   Id id_;
   const char* name_;
-  intptr_t flags_;
 };
 
 }  // namespace dart
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index 08e04f9..c1daced 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -1627,11 +1627,13 @@
     const InferredTypeMetadata* result_type,
     bool use_unchecked_entry,
     const CallSiteAttributesMetadata* call_site_attrs,
-    bool receiver_is_not_smi) {
+    bool receiver_is_not_smi,
+    bool is_call_on_this) {
   return flow_graph_builder_->InstanceCall(
       position, name, kind, type_args_len, argument_count, argument_names,
       checked_argument_count, interface_target, tearoff_interface_target,
-      result_type, use_unchecked_entry, call_site_attrs, receiver_is_not_smi);
+      result_type, use_unchecked_entry, call_site_attrs, receiver_is_not_smi,
+      is_call_on_this);
 }
 
 Fragment StreamingFlowGraphBuilder::ThrowException(TokenPosition position) {
@@ -2420,7 +2422,8 @@
   const TokenPosition position = ReadPosition();  // read position.
   if (p != nullptr) *p = position;
 
-  if (PeekTag() == kThisExpression) {
+  const bool is_call_on_this = PeekTag() == kThisExpression;
+  if (is_call_on_this) {
     is_unchecked_call = true;
   }
   instructions += BuildExpression();  // read receiver.
@@ -2464,7 +2467,8 @@
         Array::null_array(), kNumArgsChecked, interface_target,
         Function::null_function(),
         /*result_type=*/nullptr,
-        /*use_unchecked_entry=*/is_unchecked_call, &call_site_attributes);
+        /*use_unchecked_entry=*/is_unchecked_call, &call_site_attributes,
+        /*receiver_not_smi=*/false, is_call_on_this);
   }
 
   instructions += Drop();  // Drop result of the setter invocation.
@@ -2928,7 +2932,8 @@
 
   // Take note of whether the invocation is against the receiver of the current
   // function: in this case, we may skip some type checks in the callee.
-  if ((PeekTag() == kThisExpression) && !is_dynamic) {
+  const bool is_call_on_this = (PeekTag() == kThisExpression) && !is_dynamic;
+  if (is_call_on_this) {
     is_unchecked_call = true;
   }
   instructions += BuildExpression();  // read receiver.
@@ -3035,12 +3040,12 @@
                    argument_names, ICData::kNoRebind, &result_type,
                    type_args_len, /*use_unchecked_entry=*/is_unchecked_call);
   } else {
-    instructions +=
-        InstanceCall(position, *mangled_name, token_kind, type_args_len,
-                     argument_count, argument_names, checked_argument_count,
-                     *interface_target, Function::null_function(), &result_type,
-                     /*use_unchecked_entry=*/is_unchecked_call,
-                     &call_site_attributes, result_type.ReceiverNotInt());
+    instructions += InstanceCall(
+        position, *mangled_name, token_kind, type_args_len, argument_count,
+        argument_names, checked_argument_count, *interface_target,
+        Function::null_function(), &result_type,
+        /*use_unchecked_entry=*/is_unchecked_call, &call_site_attributes,
+        result_type.ReceiverNotInt(), is_call_on_this);
   }
 
   // Drop temporaries preserving result on the top of the stack.
@@ -3880,12 +3885,10 @@
     // We do not care whether the 'as' cast as implicitly added by the frontend
     // or explicitly written by the user, in both cases we use an assert
     // assignable.
-    instructions += LoadLocal(MakeTemporary());
     instructions += B->AssertAssignableLoadTypeArguments(
         position, type,
         is_type_error ? Symbols::Empty() : Symbols::InTypeCast(),
         AssertAssignableInstr::kInsertedByFrontend);
-    instructions += Drop();
   }
   return instructions;
 }
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
index 0f40918..47ed58b 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
@@ -199,7 +199,8 @@
       const InferredTypeMetadata* result_type = nullptr,
       bool use_unchecked_entry = false,
       const CallSiteAttributesMetadata* call_site_attrs = nullptr,
-      bool receiver_is_not_smi = false);
+      bool receiver_is_not_smi = false,
+      bool is_call_on_this = false);
 
   Fragment ThrowException(TokenPosition position);
   Fragment BooleanNegate();
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index efd2f8e..c993d10 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -347,7 +347,8 @@
     const InferredTypeMetadata* result_type,
     bool use_unchecked_entry,
     const CallSiteAttributesMetadata* call_site_attrs,
-    bool receiver_is_not_smi) {
+    bool receiver_is_not_smi,
+    bool is_call_on_this) {
   const intptr_t total_count = argument_count + (type_args_len > 0 ? 1 : 0);
   InputsArray* arguments = GetArguments(total_count);
   InstanceCallInstr* call = new (Z) InstanceCallInstr(
@@ -360,6 +361,9 @@
   if (use_unchecked_entry) {
     call->set_entry_kind(Code::EntryKind::kUnchecked);
   }
+  if (is_call_on_this) {
+    call->mark_as_call_on_this();
+  }
   if (call_site_attrs != nullptr && call_site_attrs->receiver_type != nullptr &&
       call_site_attrs->receiver_type->IsInstantiated()) {
     call->set_receivers_static_type(call_site_attrs->receiver_type);
@@ -1769,7 +1773,7 @@
   if (auto const alloc = definition->AsAllocateClosure()) {
     return !alloc->known_function().IsNull();
   }
-  return definition->IsLoadLocal();
+  return definition->IsLoadLocal() || definition->IsAssertAssignable();
 }
 
 Fragment FlowGraphBuilder::EvaluateAssertion() {
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h
index 6dfc141..42573d0 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.h
+++ b/runtime/vm/compiler/frontend/kernel_to_il.h
@@ -191,7 +191,8 @@
       const InferredTypeMetadata* result_type = nullptr,
       bool use_unchecked_entry = false,
       const CallSiteAttributesMetadata* call_site_attrs = nullptr,
-      bool receiver_is_not_smi = false);
+      bool receiver_is_not_smi = false,
+      bool is_call_on_this = false);
 
   Fragment FfiCall(const compiler::ffi::CallMarshaller& marshaller);
 
diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h
index ee81b99..50640ad 100644
--- a/runtime/vm/symbols.h
+++ b/runtime/vm/symbols.h
@@ -470,7 +470,8 @@
   V(vm_trace_entrypoints, "vm:testing.unsafe.trace-entrypoints-fn")            \
   V(vm_ffi_struct_fields, "vm:ffi:struct-fields")                              \
   V(vm_unsafe_no_interrupts, "vm:unsafe:no-interrupts")                        \
-  V(vm_external_name, "vm:external-name")
+  V(vm_external_name, "vm:external-name")                                      \
+  V(vm_testing_print_flow_graph, "vm:testing:print-flow-graph")
 
 // Contains a list of frequently used strings in a canonicalized form. This
 // list is kept in the vm_isolate in order to share the copy across isolates
diff --git a/tools/VERSION b/tools/VERSION
index fb4f871..c188fa2 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 16
 PATCH 0
-PRERELEASE 16
+PRERELEASE 17
 PRERELEASE_PATCH 0
\ No newline at end of file