Version 2.16.0-21.0.dev

Merge commit '2007dadb9c66c1f859de5c851ea4dc5f072b5746' into 'dev'
diff --git a/pkg/front_end/test/fasta/messages_suite.dart b/pkg/front_end/test/fasta/messages_suite.dart
index 99579c9..df8fbb5 100644
--- a/pkg/front_end/test/fasta/messages_suite.dart
+++ b/pkg/front_end/test/fasta/messages_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:convert" show utf8;
 
 import 'dart:io' show File, Platform;
@@ -66,23 +64,23 @@
 
   final YamlMap data;
 
-  final Example example;
+  final Example? example;
 
-  final String problem;
+  final String? problem;
 
   MessageTestDescription(this.uri, this.shortName, this.name, this.data,
       this.example, this.problem);
 }
 
 class Configuration {
-  final NnbdMode nnbdMode;
+  final NnbdMode? nnbdMode;
   final Set<InvocationMode> invocationModes;
 
   const Configuration(this.nnbdMode, this.invocationModes);
 
   CompilerOptions apply(CompilerOptions options) {
     if (nnbdMode != null) {
-      options.nnbdMode = nnbdMode;
+      options.nnbdMode = nnbdMode!;
     }
     options.invocationModes = invocationModes;
     return options;
@@ -125,7 +123,7 @@
         [spell.Dictionaries.cfeMessages],
         interactive,
         '"$dartPath" "$suitePath" -DfastOnly=true -Dinteractive=true');
-    return null;
+    return new Future.value();
   }
 
   MessageTestSuite(this.fastOnly, this.interactive)
@@ -151,30 +149,30 @@
     Uri uri = suite.uri.resolve("messages.yaml");
     File file = new File.fromUri(uri);
     String fileContent = file.readAsStringSync();
-    YamlMap messages = loadYamlNode(fileContent, sourceUrl: uri);
+    YamlMap messages = loadYamlNode(fileContent, sourceUrl: uri) as YamlMap;
     for (String name in messages.keys) {
-      YamlNode messageNode = messages.nodes[name];
-      var message = messageNode.value;
+      YamlMap messageNode = messages.nodes[name] as YamlMap;
+      dynamic message = messageNode.value;
       if (message is String) continue;
 
       List<String> unknownKeys = <String>[];
       bool exampleAllowMoreCodes = false;
       List<Example> examples = <Example>[];
-      String externalTest;
+      String? externalTest;
       bool frontendInternal = false;
-      List<String> analyzerCodes;
-      Severity severity;
-      YamlNode badSeverity;
-      YamlNode unnecessarySeverity;
+      List<String>? analyzerCodes;
+      Severity? severity;
+      YamlNode? badSeverity;
+      YamlNode? unnecessarySeverity;
       List<String> badHasPublishedDocsValue = <String>[];
-      List<String> spellingMessages;
+      List<String>? spellingMessages;
       const String spellingPostMessage = "\nIf the word(s) look okay, update "
           "'spell_checking_list_messages.txt' or "
           "'spell_checking_list_common.txt'.";
-      Configuration configuration;
-      Map<ExperimentalFlag, bool> experimentalFlags;
+      Configuration? configuration;
+      Map<ExperimentalFlag, bool>? experimentalFlags;
 
-      Source source;
+      Source? source;
       List<String> formatSpellingMistakes(spell.SpellingResult spellResult,
           int offset, String message, String messageForDenyListed) {
         if (source == null) {
@@ -189,23 +187,23 @@
           source = new Source(lineStarts, bytes, uri, uri);
         }
         List<String> result = <String>[];
-        for (int i = 0; i < spellResult.misspelledWords.length; i++) {
-          Location location = source.getLocation(
-              uri, offset + spellResult.misspelledWordsOffset[i]);
-          bool denylisted = spellResult.misspelledWordsDenylisted[i];
+        for (int i = 0; i < spellResult.misspelledWords!.length; i++) {
+          Location location = source!
+              .getLocation(uri, offset + spellResult.misspelledWordsOffset![i]);
+          bool denylisted = spellResult.misspelledWordsDenylisted![i];
           String messageToUse = message;
           if (denylisted) {
             messageToUse = messageForDenyListed;
-            reportedWordsDenylisted.add(spellResult.misspelledWords[i]);
+            reportedWordsDenylisted.add(spellResult.misspelledWords![i]);
           } else {
-            reportedWords.add(spellResult.misspelledWords[i]);
+            reportedWords.add(spellResult.misspelledWords![i]);
           }
           result.add(command_line_reporting.formatErrorMessage(
-              source.getTextLine(location.line),
+              source!.getTextLine(location.line),
               location,
-              spellResult.misspelledWords[i].length,
+              spellResult.misspelledWords![i].length,
               relativize(uri),
-              "$messageToUse: '${spellResult.misspelledWords[i]}'."));
+              "$messageToUse: '${spellResult.misspelledWords![i]}'."));
         }
         return result;
       }
@@ -280,11 +278,12 @@
             break;
 
           case "bytes":
-            YamlList list = node;
+            YamlList list = node as YamlList;
             if (list.first is List) {
-              for (YamlList bytes in list.nodes) {
+              for (YamlNode bytes in list.nodes) {
                 int i = 0;
-                examples.add(new BytesExample("bytes${++i}", name, bytes));
+                examples.add(
+                    new BytesExample("bytes${++i}", name, bytes as YamlList));
               }
             } else {
               examples.add(new BytesExample("bytes", name, list));
@@ -355,7 +354,7 @@
 
           case "configuration":
             if (value is String) {
-              NnbdMode nnbdMode;
+              NnbdMode? nnbdMode;
               Set<InvocationMode> invocationModes = {};
               for (String part in value.split(',')) {
                 if (part.isEmpty) continue;
@@ -364,7 +363,8 @@
                 } else if (part == "nnbd-strong") {
                   nnbdMode = NnbdMode.Strong;
                 } else {
-                  InvocationMode invocationMode = InvocationMode.fromName(part);
+                  InvocationMode? invocationMode =
+                      InvocationMode.fromName(part);
                   if (invocationMode != null) {
                     invocationModes.add(invocationMode);
                   } else {
@@ -418,7 +418,7 @@
       }
 
       MessageTestDescription createDescription(
-          String subName, Example example, String problem,
+          String subName, Example? example, String? problem,
           {location}) {
         String shortName = "$name/$subName";
         if (problem != null) {
@@ -467,7 +467,7 @@
           badSeverity != null
               ? "Unknown severity: '${badSeverity.value}'."
               : null,
-          location: badSeverity?.span?.start);
+          location: badSeverity?.span.start);
 
       yield createDescription(
           "unnecessarySeverity",
@@ -475,7 +475,7 @@
           unnecessarySeverity != null
               ? "The 'ERROR' severity is the default and not necessary."
               : null,
-          location: unnecessarySeverity?.span?.start);
+          location: unnecessarySeverity?.span.start);
 
       yield createDescription(
           "spelling",
@@ -528,7 +528,7 @@
     var span = example.node.span;
     StringBuffer buffer = new StringBuffer();
     buffer
-      ..write(relativize(span.sourceUrl))
+      ..write(relativize(span.sourceUrl!))
       ..write(":")
       ..write(span.start.line + 1)
       ..write(":")
@@ -537,7 +537,7 @@
       ..write(message);
     buffer.write("\n${span.text}");
     for (DiagnosticMessage message in messages) {
-      buffer.write("\nCode: ${getMessageCodeObject(message).name}");
+      buffer.write("\nCode: ${getMessageCodeObject(message)!.name}");
       buffer.write("\n  > ");
       buffer.write(
           message.plainTextFormatted.join("\n").replaceAll("\n", "\n  > "));
@@ -554,9 +554,9 @@
 
   bool allowMoreCodes = false;
 
-  Configuration configuration;
+  late Configuration configuration;
 
-  Map<ExperimentalFlag, bool> experimentalFlags;
+  Map<ExperimentalFlag, bool>? experimentalFlags;
 
   Example(this.name, this.expectedCode);
 
@@ -677,7 +677,7 @@
       });
       return scriptFiles;
     } else {
-      return {mainFilename: new Script.fromSource(script)};
+      return {mainFilename: new Script.fromSource(script as String)};
     }
   }
 }
@@ -709,7 +709,7 @@
       throw "Framework failure: "
           "Wanted to create wrapper file, but the file already exists!";
     }
-    Script originalMainScript = scriptFiles[example.mainFilename];
+    Script originalMainScript = scriptFiles[example.mainFilename]!;
     String preamble = originalMainScript.preamble;
     scriptFiles[mainFilename] = new Script.fromSource("""
 ${preamble}part "${example.mainFilename}";
@@ -718,7 +718,7 @@
     // Modify the original main file to be part of the wrapper and add lots of
     // gunk so every actual position in the file is not a valid position in the
     // wrapper.
-    String originalMainSource = originalMainScript.sourceWithoutPreamble;
+    String? originalMainSource = originalMainScript.sourceWithoutPreamble;
     String partPrefix = """
 ${preamble}part of "${mainFilename}";
 // La la la la la la la la la la la la la.
@@ -746,14 +746,15 @@
   YamlNode get node => example.node;
 }
 
-class Validate extends Step<MessageTestDescription, Example, MessageTestSuite> {
+class Validate
+    extends Step<MessageTestDescription, Example?, MessageTestSuite> {
   const Validate();
 
   @override
   String get name => "validate";
 
   @override
-  Future<Result<Example>> run(
+  Future<Result<Example?>> run(
       MessageTestDescription description, MessageTestSuite suite) {
     if (description.problem != null) {
       return new Future.value(fail(null, description.problem));
@@ -763,14 +764,14 @@
   }
 }
 
-class Compile extends Step<Example, Null, MessageTestSuite> {
+class Compile extends Step<Example?, Null, MessageTestSuite> {
   const Compile();
 
   @override
   String get name => "compile";
 
   @override
-  Future<Result<Null>> run(Example example, MessageTestSuite suite) async {
+  Future<Result<Null>> run(Example? example, MessageTestSuite suite) async {
     if (example == null) return pass(null);
     String dir = "${example.expectedCode}/${example.name}";
     example.scripts.forEach((String fileName, Script script) {
@@ -812,14 +813,14 @@
     if (example.allowMoreCodes) {
       List<DiagnosticMessage> messagesFiltered = <DiagnosticMessage>[];
       for (DiagnosticMessage message in messages) {
-        if (getMessageCodeObject(message).name == example.expectedCode) {
+        if (getMessageCodeObject(message)!.name == example.expectedCode) {
           messagesFiltered.add(message);
         }
       }
       messages = messagesFiltered;
     }
     for (DiagnosticMessage message in messages) {
-      if (getMessageCodeObject(message).name != example.expectedCode) {
+      if (getMessageCodeObject(message)!.name != example.expectedCode) {
         unexpectedMessages.add(message);
       }
     }
@@ -868,7 +869,7 @@
 class Script {
   final Uint8List bytes;
   final String preamble;
-  final String sourceWithoutPreamble;
+  final String? sourceWithoutPreamble;
 
   Script(this.bytes, this.preamble, this.sourceWithoutPreamble);
 
diff --git a/pkg/front_end/test/fasta/object_supertype_test.dart b/pkg/front_end/test/fasta/object_supertype_test.dart
index b588191..819a3a3 100644
--- a/pkg/front_end/test/fasta/object_supertype_test.dart
+++ b/pkg/front_end/test/fasta/object_supertype_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:convert" show json;
 
 import "package:_fe_analyzer_shared/src/messages/diagnostic_message.dart"
@@ -77,13 +75,13 @@
 
 Future<void> test() async {
   Set<String> normalErrors = (await outline("class Object {"))
-      .map((DiagnosticMessage message) => getMessageCodeObject(message).name)
+      .map((DiagnosticMessage message) => getMessageCodeObject(message)!.name)
       .toSet();
 
   Future<void> check(String objectHeader, List<Code> expectedCodes) async {
     List<DiagnosticMessage> messages = (await outline(objectHeader))
         .where((DiagnosticMessage message) =>
-            !normalErrors.contains(getMessageCodeObject(message).name))
+            !normalErrors.contains(getMessageCodeObject(message)!.name))
         .toList();
     Expect.setEquals(
         expectedCodes,
diff --git a/pkg/front_end/test/fasta/reexport_test.dart b/pkg/front_end/test/fasta/reexport_test.dart
index 5972e6a..0e46523 100644
--- a/pkg/front_end/test/fasta/reexport_test.dart
+++ b/pkg/front_end/test/fasta/reexport_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" show compileUnit;
diff --git a/pkg/front_end/test/fasta/sdk_test.dart b/pkg/front_end/test/fasta/sdk_test.dart
index 54caf22..c448d5d 100644
--- a/pkg/front_end/test/fasta/sdk_test.dart
+++ b/pkg/front_end/test/fasta/sdk_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
-
 library fasta.test.sdk_test;
 
 import 'testing/suite.dart';
diff --git a/pkg/front_end/test/fasta/super_mixins_test.dart b/pkg/front_end/test/fasta/super_mixins_test.dart
index 8f97ec7..2ef4893 100644
--- a/pkg/front_end/test/fasta/super_mixins_test.dart
+++ b/pkg/front_end/test/fasta/super_mixins_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
-
 library fasta.test.incremental_dynamic_test;
 
 import "package:_fe_analyzer_shared/src/messages/diagnostic_message.dart"
@@ -83,7 +81,7 @@
     Expect.equals(Severity.error, message.severity);
     Expect.identical(codeSuperclassHasNoMethod, getMessageCodeObject(message));
     Expect.isTrue(message.plainTextFormatted.length == 1);
-    names.add(getMessageArguments(message)['name']);
+    names.add(getMessageArguments(message)!['name']);
   };
 }
 
diff --git a/pkg/front_end/tool/_fasta/entry_points.dart b/pkg/front_end/tool/_fasta/entry_points.dart
index 2aa92c9..70b5606 100644
--- a/pkg/front_end/tool/_fasta/entry_points.dart
+++ b/pkg/front_end/tool/_fasta/entry_points.dart
@@ -126,7 +126,7 @@
 }
 
 class BatchCompiler {
-  final Stream<String> lines;
+  final Stream<String>? lines;
 
   Uri? platformUri;
 
@@ -137,7 +137,7 @@
   BatchCompiler(this.lines);
 
   Future<void> run() async {
-    await for (String line in lines) {
+    await for (String line in lines!) {
       try {
         if (await batchCompileArguments(
             new List<String>.from(jsonDecode(line)))) {
diff --git a/pkg/vm/bin/compare_il.dart b/pkg/vm/bin/compare_il.dart
index 8196a5e..5b9caa3 100644
--- a/pkg/vm/bin/compare_il.dart
+++ b/pkg/vm/bin/compare_il.dart
@@ -138,7 +138,7 @@
   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);
+        FlowGraph(m['b'], m['desc'], m['flags'], rename: rename);
   }
 
   return graphs;
diff --git a/pkg/vm/lib/testing/il_matchers.dart b/pkg/vm/lib/testing/il_matchers.dart
index dd5eb8f..1c2f883 100644
--- a/pkg/vm/lib/testing/il_matchers.dart
+++ b/pkg/vm/lib/testing/il_matchers.dart
@@ -12,14 +12,18 @@
 class FlowGraph {
   final List<dynamic> blocks;
   final Map<String, InstructionDescriptor> descriptors;
+  final Map<String, dynamic> flags;
   final Renamer rename;
 
-  FlowGraph(this.blocks, Map<String, dynamic> desc, {required this.rename})
+  FlowGraph(this.blocks, Map<String, dynamic> desc, this.flags,
+      {required this.rename})
       : descriptors = {
           for (var e in desc.entries)
             e.key: InstructionDescriptor.fromJson(e.value)
         };
 
+  bool get soundNullSafety => flags['nnbd'];
+
   /// 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
@@ -33,11 +37,34 @@
     env ??= Env(rename: rename, descriptors: descriptors);
 
     for (var i = 0; i < expected.length; i++) {
-      expected[i].match(env, blocks[i]).expectMatched('failed to match');
+      final result = expected[i].match(env, blocks[i]);
+      if (result.isFail) {
+        print('Failed to match: ${result.message}');
+        dump();
+        throw 'Failed to match';
+      }
     }
 
     return env;
   }
+
+  void dump() {
+    for (var block in blocks) {
+      print('B${block['b']}[${block['o']}]');
+      for (var instr in [...?block['d'], ...?block['is']]) {
+        final v = instr['v'] ?? -1;
+        final prefix = v != -1 ? 'v$v <- ' : '';
+        final inputs = instr['i']?.map((v) => 'v$v').join(', ') ?? '';
+        final attrs = descriptors[instr['o']]
+            ?.attributeIndex
+            .entries
+            .map((e) => '${e.key}: ${instr['d'][e.value]}')
+            .join(',');
+        final attrsWrapped = attrs != null ? '[$attrs]' : '';
+        print('  ${prefix}${instr['o']}$attrsWrapped($inputs)');
+      }
+    }
+  }
 }
 
 class InstructionDescriptor {
@@ -304,6 +331,11 @@
         .toList());
     return impl!.match(e, v);
   }
+
+  @override
+  String toString() {
+    return matchers.toString();
+  }
 }
 
 /// Matcher which matches an instruction with opcode [op] and properties
diff --git a/runtime/tests/vm/dart/flutter_regress_91370_il_test.dart b/runtime/tests/vm/dart/flutter_regress_91370_il_test.dart
index 722da15..afa6bcf 100644
--- a/runtime/tests/vm/dart/flutter_regress_91370_il_test.dart
+++ b/runtime/tests/vm/dart/flutter_regress_91370_il_test.dart
@@ -221,8 +221,8 @@
   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());
+    final a1 = A1("$i");
+    final a0 = A0(i.toDouble(), "$i");
     B1(a1).testNarrowingThroughThisCallWithPositionalParam(H(a1));
     B0(a0).testNarrowingThroughThisCallWithPositionalParam(H(a0));
     B1(a1).testNarrowingThroughThisCallWithNamedParams(H(a1));
@@ -601,7 +601,6 @@
     ]),
     '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'),
@@ -617,8 +616,13 @@
         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'),
+          'a.data[0]*' << match.Redefinition('a.data[0]'),
+          if (!beforeLICM.soundNullSafety)
+            'a.data[0]*!' << match.CheckNull('a.data[0]*'),
+          'a.data[0].str' <<
+              match.LoadField(
+                  beforeLICM.soundNullSafety ? 'a.data[0]*' : 'a.data[0]*!',
+                  slot: 'str'),
         ]),
   ]);
 
@@ -632,7 +636,6 @@
     ]),
     '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'),
@@ -646,7 +649,12 @@
         ]),
     'B3' <<
         match.block('Target', [
-          'a.data[0].str' << match.LoadField('a.data[0]*', slot: 'str'),
+          if (!beforeLICM.soundNullSafety)
+            'a.data[0]!' << match.CheckNull('a.data[0]'),
+          'a.data[0].str' <<
+              match.LoadField(
+                  beforeLICM.soundNullSafety ? 'a.data[0]' : 'a.data[0]!',
+                  slot: 'str'),
         ]),
   ], env: env);
 }
@@ -668,7 +676,6 @@
     ]),
     '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'),
@@ -684,8 +691,11 @@
         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'),
+          'a[0]*' << match.Redefinition('a[0]'),
+          if (!beforeLICM.soundNullSafety) 'a[0]*!' << match.CheckNull('a[0]*'),
+          'a[0].str' <<
+              match.LoadField(beforeLICM.soundNullSafety ? 'a[0]*' : 'a[0]*!',
+                  slot: 'str'),
         ]),
   ]);
 
@@ -698,7 +708,6 @@
     ]),
     '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'),
@@ -712,7 +721,10 @@
         ]),
     'B3' <<
         match.block('Target', [
-          'a[0].str' << match.LoadField('a[0]*', slot: 'str'),
+          if (!beforeLICM.soundNullSafety) 'a[0]!' << match.CheckNull('a[0]'),
+          'a[0].str' <<
+              match.LoadField(beforeLICM.soundNullSafety ? 'a[0]' : 'a[0]!',
+                  slot: 'str'),
         ]),
   ], env: env);
 }
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
index 722da15..afa6bcf 100644
--- a/runtime/tests/vm/dart_2/flutter_regress_91370_il_test.dart
+++ b/runtime/tests/vm/dart_2/flutter_regress_91370_il_test.dart
@@ -221,8 +221,8 @@
   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());
+    final a1 = A1("$i");
+    final a0 = A0(i.toDouble(), "$i");
     B1(a1).testNarrowingThroughThisCallWithPositionalParam(H(a1));
     B0(a0).testNarrowingThroughThisCallWithPositionalParam(H(a0));
     B1(a1).testNarrowingThroughThisCallWithNamedParams(H(a1));
@@ -601,7 +601,6 @@
     ]),
     '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'),
@@ -617,8 +616,13 @@
         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'),
+          'a.data[0]*' << match.Redefinition('a.data[0]'),
+          if (!beforeLICM.soundNullSafety)
+            'a.data[0]*!' << match.CheckNull('a.data[0]*'),
+          'a.data[0].str' <<
+              match.LoadField(
+                  beforeLICM.soundNullSafety ? 'a.data[0]*' : 'a.data[0]*!',
+                  slot: 'str'),
         ]),
   ]);
 
@@ -632,7 +636,6 @@
     ]),
     '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'),
@@ -646,7 +649,12 @@
         ]),
     'B3' <<
         match.block('Target', [
-          'a.data[0].str' << match.LoadField('a.data[0]*', slot: 'str'),
+          if (!beforeLICM.soundNullSafety)
+            'a.data[0]!' << match.CheckNull('a.data[0]'),
+          'a.data[0].str' <<
+              match.LoadField(
+                  beforeLICM.soundNullSafety ? 'a.data[0]' : 'a.data[0]!',
+                  slot: 'str'),
         ]),
   ], env: env);
 }
@@ -668,7 +676,6 @@
     ]),
     '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'),
@@ -684,8 +691,11 @@
         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'),
+          'a[0]*' << match.Redefinition('a[0]'),
+          if (!beforeLICM.soundNullSafety) 'a[0]*!' << match.CheckNull('a[0]*'),
+          'a[0].str' <<
+              match.LoadField(beforeLICM.soundNullSafety ? 'a[0]*' : 'a[0]*!',
+                  slot: 'str'),
         ]),
   ]);
 
@@ -698,7 +708,6 @@
     ]),
     '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'),
@@ -712,7 +721,10 @@
         ]),
     'B3' <<
         match.block('Target', [
-          'a[0].str' << match.LoadField('a[0]*', slot: 'str'),
+          if (!beforeLICM.soundNullSafety) 'a[0]!' << match.CheckNull('a[0]'),
+          'a[0].str' <<
+              match.LoadField(beforeLICM.soundNullSafety ? 'a[0]' : 'a[0]!',
+                  slot: 'str'),
         ]),
   ], env: env);
 }
diff --git a/runtime/vm/app_snapshot.cc b/runtime/vm/app_snapshot.cc
index 11fe806..7049265 100644
--- a/runtime/vm/app_snapshot.cc
+++ b/runtime/vm/app_snapshot.cc
@@ -5686,43 +5686,48 @@
 
 class ProgramSerializationRoots : public SerializationRoots {
  public:
+#define RESET_ROOT_LIST(V)                                                     \
+  V(symbol_table, Array, HashTables::New<CanonicalStringSet>(4))               \
+  V(canonical_types, Array, HashTables::New<CanonicalTypeSet>(4))              \
+  V(canonical_function_types, Array,                                           \
+    HashTables::New<CanonicalFunctionTypeSet>(4))                              \
+  V(canonical_type_arguments, Array,                                           \
+    HashTables::New<CanonicalTypeArgumentsSet>(4))                             \
+  V(canonical_type_parameters, Array,                                          \
+    HashTables::New<CanonicalTypeParameterSet>(4))                             \
+  ONLY_IN_PRODUCT(ONLY_IN_AOT(                                                 \
+      V(closure_functions, GrowableObjectArray, GrowableObjectArray::null())))
+
   ProgramSerializationRoots(ZoneGrowableArray<Object*>* base_objects,
                             ObjectStore* object_store,
                             Snapshot::Kind snapshot_kind)
       : base_objects_(base_objects),
         object_store_(object_store),
-        dispatch_table_entries_(Array::Handle()),
-        saved_symbol_table_(Array::Handle()),
-        saved_canonical_types_(Array::Handle()),
-        saved_canonical_function_types_(Array::Handle()),
-        saved_canonical_type_arguments_(Array::Handle()),
-        saved_canonical_type_parameters_(Array::Handle()) {
-    saved_symbol_table_ = object_store->symbol_table();
-    object_store->set_symbol_table(
-        Array::Handle(HashTables::New<CanonicalStringSet>(4)));
-    saved_canonical_types_ = object_store->canonical_types();
-    object_store->set_canonical_types(
-        Array::Handle(HashTables::New<CanonicalTypeSet>(4)));
-    saved_canonical_function_types_ = object_store->canonical_function_types();
-    object_store->set_canonical_function_types(
-        Array::Handle(HashTables::New<CanonicalFunctionTypeSet>(4)));
-    saved_canonical_type_arguments_ = object_store->canonical_type_arguments();
-    object_store->set_canonical_type_arguments(
-        Array::Handle(HashTables::New<CanonicalTypeArgumentsSet>(4)));
-    saved_canonical_type_parameters_ =
-        object_store->canonical_type_parameters();
-    object_store->set_canonical_type_parameters(
-        Array::Handle(HashTables::New<CanonicalTypeParameterSet>(4)));
+        snapshot_kind_(snapshot_kind) {
+#define ONLY_IN_AOT(code)                                                      \
+  if (snapshot_kind_ == Snapshot::kFullAOT) {                                  \
+    code                                                                       \
+  }
+#define SAVE_AND_RESET_ROOT(name, Type, init)                                  \
+  do {                                                                         \
+    saved_##name##_ = object_store->name();                                    \
+    object_store->set_##name(Type::Handle(init));                              \
+  } while (0);
+
+    RESET_ROOT_LIST(SAVE_AND_RESET_ROOT)
+#undef SAVE_AND_RESET_ROOT
+#undef ONLY_IN_AOT
   }
   ~ProgramSerializationRoots() {
-    object_store_->set_symbol_table(saved_symbol_table_);
-    object_store_->set_canonical_types(saved_canonical_types_);
-    object_store_->set_canonical_function_types(
-        saved_canonical_function_types_);
-    object_store_->set_canonical_type_arguments(
-        saved_canonical_type_arguments_);
-    object_store_->set_canonical_type_parameters(
-        saved_canonical_type_parameters_);
+#define ONLY_IN_AOT(code)                                                      \
+  if (snapshot_kind_ == Snapshot::kFullAOT) {                                  \
+    code                                                                       \
+  }
+#define RESTORE_ROOT(name, Type, init)                                         \
+  object_store_->set_##name(saved_##name##_);
+    RESET_ROOT_LIST(RESTORE_ROOT)
+#undef RESTORE_ROOT
+#undef ONLY_IN_AOT
   }
 
   void AddBaseObjects(Serializer* s) {
@@ -5776,14 +5781,16 @@
   }
 
  private:
-  ZoneGrowableArray<Object*>* base_objects_;
-  ObjectStore* object_store_;
-  Array& dispatch_table_entries_;
-  Array& saved_symbol_table_;
-  Array& saved_canonical_types_;
-  Array& saved_canonical_function_types_;
-  Array& saved_canonical_type_arguments_;
-  Array& saved_canonical_type_parameters_;
+  ZoneGrowableArray<Object*>* const base_objects_;
+  ObjectStore* const object_store_;
+  const Snapshot::Kind snapshot_kind_;
+  Array& dispatch_table_entries_ = Array::Handle();
+
+#define ONLY_IN_AOT(code) code
+#define DECLARE_FIELD(name, Type, init) Type& saved_##name##_ = Type::Handle();
+  RESET_ROOT_LIST(DECLARE_FIELD)
+#undef DECLARE_FIELD
+#undef ONLY_IN_AOT
 };
 #endif  // !DART_PRECOMPILED_RUNTIME
 
diff --git a/runtime/vm/closure_functions_cache.cc b/runtime/vm/closure_functions_cache.cc
index ff62680..49badbb 100644
--- a/runtime/vm/closure_functions_cache.cc
+++ b/runtime/vm/closure_functions_cache.cc
@@ -73,7 +73,9 @@
   return Function::null();
 }
 
-void ClosureFunctionsCache::AddClosureFunctionLocked(const Function& function) {
+void ClosureFunctionsCache::AddClosureFunctionLocked(
+    const Function& function,
+    bool allow_implicit_closure_functions /* = false */) {
   ASSERT(!Compiler::IsBackgroundCompilation());
 
   auto thread = Thread::Current();
@@ -86,7 +88,8 @@
   const auto& closures =
       GrowableObjectArray::Handle(zone, object_store->closure_functions());
   ASSERT(!closures.IsNull());
-  ASSERT(function.IsNonImplicitClosureFunction());
+  ASSERT(allow_implicit_closure_functions ||
+         function.IsNonImplicitClosureFunction());
   closures.Add(function, Heap::kOld);
 }
 
diff --git a/runtime/vm/closure_functions_cache.h b/runtime/vm/closure_functions_cache.h
index 019c12b..c52c2e6 100644
--- a/runtime/vm/closure_functions_cache.h
+++ b/runtime/vm/closure_functions_cache.h
@@ -49,7 +49,13 @@
   static FunctionPtr LookupClosureFunctionLocked(const Function& parent,
                                                  TokenPosition token_pos);
 
-  static void AddClosureFunctionLocked(const Function& function);
+  // Normally implicit closure functions are not added to this cache, however
+  // during AOT compilation we might add those implicit closure functions
+  // that have their original functions shaken to allow ProgramWalker to
+  // discover them.
+  static void AddClosureFunctionLocked(
+      const Function& function,
+      bool allow_implicit_closure_functions = false);
 
   static intptr_t FindClosureIndex(const Function& needle);
   static FunctionPtr ClosureFunctionFromIndex(intptr_t idx);
diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc
index 87d430f..9fefdca 100644
--- a/runtime/vm/compiler/aot/precompiler.cc
+++ b/runtime/vm/compiler/aot/precompiler.cc
@@ -141,12 +141,11 @@
   // The object is a function and symbolic stack traces are enabled.
   static constexpr const char* kSymbolicStackTraces =
       "needed for symbolic stack traces";
-  // The object is a function that is only used via its implicit closure
-  // function, into which it was inlined.
-  static constexpr const char* kInlinedIntoICF =
-      "inlined into implicit closure function";
   // The object is a parent function function of a non-inlined local function.
   static constexpr const char* kLocalParent = "parent of a local function";
+  // The object is a main function of the root library.
+  static constexpr const char* kMainFunction =
+      "this is main function of the root library";
   // The object has an entry point pragma that requires it be retained.
   static constexpr const char* kEntryPointPragma = "entry point pragma";
   // The function is a target of FFI callback.
@@ -724,13 +723,10 @@
 
 void Precompiler::AddRoots() {
   HANDLESCOPE(T);
-  // Note that <rootlibrary>.main is not a root. The appropriate main will be
-  // discovered through _getMainClosure.
-
   AddSelector(Symbols::NoSuchMethod());
-
   AddSelector(Symbols::Call());  // For speed, not correctness.
 
+  // Add main as an entry point.
   const Library& lib = Library::Handle(IG->object_store()->root_library());
   if (lib.IsNull()) {
     const String& msg = String::Handle(
@@ -740,22 +736,26 @@
   }
 
   const String& name = String::Handle(String::New("main"));
-  const Object& main_closure = Object::Handle(lib.GetFunctionClosure(name));
-  if (main_closure.IsClosure()) {
-    if (lib.LookupLocalFunction(name) == Function::null()) {
-      // Check whether the function is in exported namespace of library, in
-      // this case we have to retain the root library caches.
-      if (lib.LookupFunctionAllowPrivate(name) != Function::null() ||
-          lib.LookupReExport(name) != Object::null()) {
-        retain_root_library_caches_ = true;
-      }
+  Function& main = Function::Handle(lib.LookupFunctionAllowPrivate(name));
+  if (main.IsNull()) {
+    const Object& obj = Object::Handle(lib.LookupReExport(name));
+    if (obj.IsFunction()) {
+      main ^= obj.ptr();
     }
-    AddConstObject(Closure::Cast(main_closure));
-  } else if (main_closure.IsError()) {
-    const Error& error = Error::Cast(main_closure);
-    String& msg =
-        String::Handle(Z, String::NewFormatted("Cannot find main closure %s\n",
-                                               error.ToErrorCString()));
+  }
+  if (!main.IsNull()) {
+    if (lib.LookupLocalFunction(name) == Function::null()) {
+      retain_root_library_caches_ = true;
+    }
+    AddRetainReason(main, RetainReasons::kMainFunction);
+    AddTypesOf(main);
+    // Create closure object from main.
+    main = main.ImplicitClosureFunction();
+    AddConstObject(Closure::Handle(main.ImplicitStaticClosure()));
+  } else {
+    String& msg = String::Handle(
+        Z, String::NewFormatted("Cannot find main in library %s\n",
+                                lib.ToCString()));
     Jump(Error::Handle(Z, ApiError::New(msg)));
     UNREACHABLE();
   }
@@ -1930,25 +1930,25 @@
       for (intptr_t j = 0; j < functions.Length(); j++) {
         SafepointWriteRwLocker ml(T, T->isolate_group()->program_lock());
         function ^= functions.At(j);
-        bool retain = possibly_retained_functions_.ContainsKey(function);
-        if (!retain && function.HasImplicitClosureFunction()) {
-          // It can happen that all uses of an implicit closure inline their
-          // target function, leaving the target function uncompiled. Keep
-          // the target function anyway so we can enumerate it to bind its
-          // static calls, etc.
-          function2 = function.ImplicitClosureFunction();
-          retain = function2.HasCode();
-          if (retain) {
-            AddRetainReason(function, RetainReasons::kInlinedIntoICF);
-          }
-        }
-        if (retain) {
-          function.DropUncompiledImplicitClosureFunction();
+        function.DropUncompiledImplicitClosureFunction();
+
+        const bool retained =
+            possibly_retained_functions_.ContainsKey(function);
+        if (retained) {
           AddTypesOf(function);
-          if (function.HasImplicitClosureFunction()) {
-            function2 = function.ImplicitClosureFunction();
-            if (possibly_retained_functions_.ContainsKey(function2)) {
-              AddTypesOf(function2);
+        }
+        if (function.HasImplicitClosureFunction()) {
+          function2 = function.ImplicitClosureFunction();
+
+          if (possibly_retained_functions_.ContainsKey(function2)) {
+            AddTypesOf(function2);
+            // If function has @pragma('vm:entry-point', 'get') we need to keep
+            // the function itself around so that runtime could find it and
+            // get to the implicit closure through it.
+            if (!retained &&
+                functions_with_entry_point_pragmas_.ContainsKey(function2)) {
+              AddRetainReason(function, RetainReasons::kEntryPointPragma);
+              AddTypesOf(function);
             }
           }
         }
@@ -2129,6 +2129,7 @@
   Array& functions = Array::Handle(Z);
   Function& function = Function::Handle(Z);
   Function& target = Function::Handle(Z);
+  Function& implicit_closure = Function::Handle(Z);
   Code& code = Code::Handle(Z);
   Object& owner = Object::Handle(Z);
   GrowableObjectArray& retained_functions = GrowableObjectArray::Handle(Z);
@@ -2207,6 +2208,17 @@
           owner, Smi::Handle(Smi::New(owner.GetClassId())));
       code.set_owner(owner);
     }
+    if (function.HasImplicitClosureFunction()) {
+      // If we are going to drop the function which has a compiled
+      // implicit closure move the closure itself to the list of closures
+      // attached to the object store so that ProgramVisitor could find it.
+      // The list of closures is going to be dropped during PRODUCT snapshotting
+      // so there is no overhead in doing so.
+      implicit_closure = function.ImplicitClosureFunction();
+      RELEASE_ASSERT(functions_to_retain_.ContainsKey(implicit_closure));
+      ClosureFunctionsCache::AddClosureFunctionLocked(
+          implicit_closure, /*allow_implicit_closure_functions=*/true);
+    }
     dropped_function_count_++;
     if (FLAG_trace_precompiler) {
       THR_Print("Dropping function %s\n",
@@ -2289,6 +2301,9 @@
     }
     return true;  // Continue iteration.
   });
+
+  // Note: in PRODUCT mode snapshotter will drop this field when serializing.
+  // This is done in ProgramSerializationRoots.
   IG->object_store()->set_closure_functions(retained_functions);
 }
 
diff --git a/runtime/vm/compiler/backend/il_printer.cc b/runtime/vm/compiler/backend/il_printer.cc
index df13a3a..f7b0d45 100644
--- a/runtime/vm/compiler/backend/il_printer.cc
+++ b/runtime/vm/compiler/backend/il_printer.cc
@@ -44,6 +44,9 @@
     writer.OpenObject("desc");
     AttributesSerializer(&writer).WriteDescriptors();
     writer.CloseObject();
+    writer.OpenObject("flags");
+    writer.PrintPropertyBool("nnbd", IsolateGroup::Current()->null_safety());
+    writer.CloseObject();
     writer.CloseObject();
     THR_Print("%s\n", writer.ToCString());
   }
diff --git a/runtime/vm/compiler/compiler_pass.cc b/runtime/vm/compiler/compiler_pass.cc
index 4bbce2c..8e34d21 100644
--- a/runtime/vm/compiler/compiler_pass.cc
+++ b/runtime/vm/compiler/compiler_pass.cc
@@ -357,6 +357,7 @@
   INVOKE_PASS(WidenSmiToInt32);
   INVOKE_PASS(SelectRepresentations);
   INVOKE_PASS(CSE);
+  INVOKE_PASS(Canonicalize);
   INVOKE_PASS(LICM);
   INVOKE_PASS(TryOptimizePatterns);
   INVOKE_PASS(DSE);
@@ -387,6 +388,7 @@
   INVOKE_PASS(AllocationSinking_DetachMaterializations);
   INVOKE_PASS(EliminateWriteBarriers);
   INVOKE_PASS(FinalizeGraph);
+  INVOKE_PASS(Canonicalize);
   INVOKE_PASS(AllocateRegisters);
   INVOKE_PASS(ReorderBlocks);
   return pass_state->flow_graph();
@@ -573,7 +575,6 @@
   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/globals.h b/runtime/vm/globals.h
index 2e63f76..46cd706 100644
--- a/runtime/vm/globals.h
+++ b/runtime/vm/globals.h
@@ -73,8 +73,10 @@
 
 #if defined(PRODUCT)
 #define NOT_IN_PRODUCT(code)
+#define ONLY_IN_PRODUCT(code) code
 #else  // defined(PRODUCT)
 #define NOT_IN_PRODUCT(code) code
+#define ONLY_IN_PRODUCT(code)
 #endif  // defined(PRODUCT)
 
 #if defined(DART_PRECOMPILED_RUNTIME) && defined(DART_PRECOMPILER)
@@ -87,13 +89,9 @@
 
 #if defined(DART_PRECOMPILED_RUNTIME)
 #define NOT_IN_PRECOMPILED(code)
-#else
-#define NOT_IN_PRECOMPILED(code) code
-#endif  // defined(DART_PRECOMPILED_RUNTIME)
-
-#if defined(DART_PRECOMPILED_RUNTIME)
 #define ONLY_IN_PRECOMPILED(code) code
 #else
+#define NOT_IN_PRECOMPILED(code) code
 #define ONLY_IN_PRECOMPILED(code)
 #endif  // defined(DART_PRECOMPILED_RUNTIME)
 
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index fd8822c..256edc0 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -7483,7 +7483,7 @@
 
 FunctionPtr Function::implicit_closure_function() const {
   if (IsClosureFunction() || IsDispatcherOrImplicitAccessor() ||
-      IsFieldInitializer() || IsFfiTrampoline()) {
+      IsFieldInitializer() || IsFfiTrampoline() || IsMethodExtractor()) {
     return Function::null();
   }
   const Object& obj = Object::Handle(data());
diff --git a/runtime/vm/object_store.cc b/runtime/vm/object_store.cc
index 33bede3..9fc1c99 100644
--- a/runtime/vm/object_store.cc
+++ b/runtime/vm/object_store.cc
@@ -103,8 +103,8 @@
                               EMIT_FIELD_INIT,
                               EMIT_FIELD_INIT)
 #undef EMIT_FIELD_INIT
-          unused_field_(0)  // Just to prevent a trailing comma.
-{
+      // Just to prevent a trailing comma.
+      unused_field_(0) {
   for (ObjectPtr* current = from(); current <= to(); current++) {
     *current = Object::null();
   }
diff --git a/tools/VERSION b/tools/VERSION
index 8e1e43b..26d24bd 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 16
 PATCH 0
-PRERELEASE 20
+PRERELEASE 21
 PRERELEASE_PATCH 0
\ No newline at end of file