Version 2.16.0-18.0.dev

Merge commit 'b74e8e5cec7cbff32945aa93a511475c4ddd9e11' into 'dev'
diff --git a/pkg/analysis_server/lib/src/lsp/source_edits.dart b/pkg/analysis_server/lib/src/lsp/source_edits.dart
index 2a5834e..49b1b09 100644
--- a/pkg/analysis_server/lib/src/lsp/source_edits.dart
+++ b/pkg/analysis_server/lib/src/lsp/source_edits.dart
@@ -180,7 +180,10 @@
       // To handle this, if both unformatted/formatted contain at least one
       // newline, split this change into two around the last newline so that the
       // final part (likely leading whitespace) can be included without
-      // including the whole change.
+      // including the whole change. This cannot be done if the newline is at
+      // the end of the source whitespace though, as this would create a split
+      // where the first part is the same and the second part is empty,
+      // resulting in an infinite loop/stack overflow.
       //
       // Without this, functionality like VS Code's "format modified lines"
       // (which uses Git status to know which lines are edited) may appear to
@@ -188,7 +191,8 @@
       if (unformattedStart < rangeStart.result &&
           unformattedEnd > rangeStart.result &&
           unformattedWhitespace.contains('\n') &&
-          formattedWhitespace.contains('\n')) {
+          formattedWhitespace.contains('\n') &&
+          !unformattedWhitespace.endsWith('\n')) {
         // Find the offsets of the character after the last newlines.
         final unformattedOffset = unformattedWhitespace.lastIndexOf('\n') + 1;
         final formattedOffset = formattedWhitespace.lastIndexOf('\n') + 1;
diff --git a/pkg/analysis_server/test/lsp/format_test.dart b/pkg/analysis_server/test/lsp/format_test.dart
index 61bac2e..7ffff8a 100644
--- a/pkg/analysis_server/test/lsp/format_test.dart
+++ b/pkg/analysis_server/test/lsp/format_test.dart
@@ -295,6 +295,26 @@
     await expectRangeFormattedContents(mainFileUri, contents, expected);
   }
 
+  Future<void> test_formatRange_trailingNewline_47702() async {
+    // Check we complete when a formatted block ends with a newline.
+    // https://github.com/dart-lang/sdk/issues/47702
+    const contents = '''
+int a;
+[[
+    int b;
+]]
+''';
+    final expected = '''
+int a;
+
+int b;
+
+''';
+    await initialize();
+    await openFile(mainFileUri, withoutMarkers(contents));
+    await expectRangeFormattedContents(mainFileUri, contents, expected);
+  }
+
   Future<void> test_invalidSyntax() async {
     const contents = '''void f(((( {
   print('test');
diff --git a/pkg/analyzer/lib/dart/ast/ast.dart b/pkg/analyzer/lib/dart/ast/ast.dart
index ba209b7..9983408 100644
--- a/pkg/analyzer/lib/dart/ast/ast.dart
+++ b/pkg/analyzer/lib/dart/ast/ast.dart
@@ -2200,6 +2200,7 @@
 ///        [BlockFunctionBody]
 ///      | [EmptyFunctionBody]
 ///      | [ExpressionFunctionBody]
+///      | [NativeFunctionBody]
 ///
 /// Clients may not extend, implement or mix-in this class.
 abstract class FunctionBody implements AstNode {
diff --git a/pkg/analyzer/lib/src/lint/pub.dart b/pkg/analyzer/lib/src/lint/pub.dart
index be237ff..8c11a57 100644
--- a/pkg/analyzer/lib/src/lint/pub.dart
+++ b/pkg/analyzer/lib/src/lint/pub.dart
@@ -207,7 +207,9 @@
   PSDependencyList? get devDependencies;
   PSEntry? get documentation;
   PSEntry? get homepage;
+  PSEntry? get issueTracker;
   PSEntry? get name;
+  PSEntry? get repository;
   PSEntry? get version;
   void accept(PubspecVisitor visitor);
 }
@@ -224,7 +226,9 @@
   T? visitPackageDevDependency(PSDependency dependency) => null;
   T? visitPackageDocumentation(PSEntry documentation) => null;
   T? visitPackageHomepage(PSEntry homepage) => null;
+  T? visitPackageIssueTracker(PSEntry issueTracker) => null;
   T? visitPackageName(PSEntry name) => null;
+  T? visitPackageRepository(PSEntry repostory) => null;
   T? visitPackageVersion(PSEntry version) => null;
 }
 
@@ -410,8 +414,12 @@
   @override
   PSEntry? homepage;
   @override
+  PSEntry? issueTracker;
+  @override
   PSEntry? name;
   @override
+  PSEntry? repository;
+  @override
   PSEntry? version;
   @override
   PSDependencyList? dependencies;
@@ -445,6 +453,12 @@
     if (homepage != null) {
       visitor.visitPackageHomepage(homepage!);
     }
+    if (issueTracker != null) {
+      visitor.visitPackageIssueTracker(issueTracker!);
+    }
+    if (repository != null) {
+      visitor.visitPackageRepository(repository!);
+    }
     if (name != null) {
       visitor.visitPackageName(name!);
     }
@@ -474,6 +488,8 @@
     sb.writelin(authors);
     sb.writelin(description);
     sb.writelin(homepage);
+    sb.writelin(repository);
+    sb.writelin(issueTracker);
     sb.writelin(dependencies);
     sb.writelin(devDependencies);
     sb.writelin(dependencyOverrides);
@@ -502,6 +518,12 @@
         case 'homepage':
           homepage = _processScalar(key, v, resourceProvider);
           break;
+        case 'repository':
+          repository = _processScalar(key, v, resourceProvider);
+          break;
+        case 'issue_tracker':
+          issueTracker = _processScalar(key, v, resourceProvider);
+          break;
         case 'name':
           name = _processScalar(key, v, resourceProvider);
           break;
diff --git a/pkg/analyzer/test/src/lint/pub_test.dart b/pkg/analyzer/test/src/lint/pub_test.dart
index 6634c41..db3a2d0 100644
--- a/pkg/analyzer/test/src/lint/pub_test.dart
+++ b/pkg/analyzer/test/src/lint/pub_test.dart
@@ -50,6 +50,8 @@
   unittest: '>=0.11.0 <0.12.0'
 dependency_overrides:
   foo: 1.2.0
+repository: https://github.com/dart-lang/linter
+issue_tracker: https://github.com/dart-lang/linter/issues
 """;
 
   Pubspec ps = Pubspec.parse(src);
@@ -72,6 +74,10 @@
       });
       testValue('homepage', ps.homepage,
           equals('https://github.com/dart-lang/linter'));
+      testValue('repository', ps.repository,
+          equals('https://github.com/dart-lang/linter'));
+      testValue('issue_tracker', ps.issueTracker,
+          equals('https://github.com/dart-lang/linter/issues'));
       testValue(
           'description', ps.description, equals('Style linter for Dart.'));
       testValue('version', ps.version, equals('0.0.1'));
diff --git a/pkg/front_end/test/analyser_ignored/load_dill_twice_test.dart b/pkg/front_end/test/analyser_ignored/load_dill_twice_test.dart
index 803f98f..2b03e4d 100644
--- a/pkg/front_end/test/analyser_ignored/load_dill_twice_test.dart
+++ b/pkg/front_end/test/analyser_ignored/load_dill_twice_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 Platform, exit;
 
 import 'package:kernel/binary/ast_from_binary.dart' show BinaryBuilder;
diff --git a/pkg/front_end/test/covariance_check/covariance_check_test.dart b/pkg/front_end/test/covariance_check/covariance_check_test.dart
index 4f59ddc..c91c012 100644
--- a/pkg/front_end/test/covariance_check/covariance_check_test.dart
+++ b/pkg/front_end/test/covariance_check/covariance_check_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, Platform;
 import 'package:_fe_analyzer_shared/src/testing/id.dart';
 import 'package:_fe_analyzer_shared/src/testing/id_testing.dart';
@@ -33,7 +31,7 @@
       InternalCompilerResult compilerResult,
       Library library,
       Map<Id, ActualData<String>> actualMap,
-      {bool verbose}) {
+      {bool? verbose}) {
     new CovarianceCheckDataExtractor(compilerResult, actualMap)
         .computeForLibrary(library);
   }
@@ -44,7 +42,7 @@
       InternalCompilerResult compilerResult,
       Member member,
       Map<Id, ActualData<String>> actualMap,
-      {bool verbose}) {
+      {bool? verbose}) {
     member.accept(new CovarianceCheckDataExtractor(compilerResult, actualMap));
   }
 
@@ -58,7 +56,7 @@
       : super(compilerResult, actualMap);
 
   @override
-  String computeNodeValue(Id id, TreeNode node) {
+  String? computeNodeValue(Id id, TreeNode node) {
     if (node is AsExpression && node.isCovarianceCheck) {
       return typeToText(node.type);
     }
diff --git a/pkg/front_end/test/enable_non_nullable/enable_non_nullable_test.dart b/pkg/front_end/test/enable_non_nullable/enable_non_nullable_test.dart
index f5fb221..a5f766e 100644
--- a/pkg/front_end/test/enable_non_nullable/enable_non_nullable_test.dart
+++ b/pkg/front_end/test/enable_non_nullable/enable_non_nullable_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:expect/expect.dart';
@@ -59,10 +57,10 @@
 }
 
 Future<void> test(
-    {bool enableNonNullableByDefault,
-    bool enableNonNullableExplicitly,
-    Version versionImpliesOptIn,
-    Version versionOptsInAllowed}) async {
+    {required bool enableNonNullableByDefault,
+    required bool enableNonNullableExplicitly,
+    required Version versionImpliesOptIn,
+    required Version versionOptsInAllowed}) async {
   CompilerOptions options = new CompilerOptions();
   if (enableNonNullableByDefault) {
     // Pretend non-nullable is on by default.
@@ -112,12 +110,12 @@
 
   Directory directory = new Directory.fromUri(
       Uri.base.resolve('pkg/front_end/test/enable_non_nullable/data/'));
-  CompilerResult result = await kernelForProgramInternal(
+  CompilerResult result = (await kernelForProgramInternal(
       directory.uri.resolve('main.dart'), options,
-      retainDataForTesting: true);
+      retainDataForTesting: true))!;
   Expect.isFalse(
       hadDiagnostic, "Compilation had diagnostics (errors, warnings)!");
-  for (Library library in result.component.libraries) {
+  for (Library library in result.component!.libraries) {
     if (library.importUri.scheme != 'dart') {
       bool usesLegacy =
           await uriUsesLegacyLanguageVersion(library.fileUri, options);
diff --git a/pkg/front_end/test/extensions/extensions_test.dart b/pkg/front_end/test/extensions/extensions_test.dart
index 5765b21..2bb86f3 100644
--- a/pkg/front_end/test/extensions/extensions_test.dart
+++ b/pkg/front_end/test/extensions/extensions_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, Platform;
 import 'package:_fe_analyzer_shared/src/testing/id.dart';
 import 'package:_fe_analyzer_shared/src/testing/id_testing.dart';
@@ -45,7 +43,7 @@
       InternalCompilerResult compilerResult,
       Member member,
       Map<Id, ActualData<Features>> actualMap,
-      {bool verbose}) {
+      {bool? verbose}) {
     member.accept(new ExtensionsDataExtractor(compilerResult, actualMap));
   }
 
@@ -55,7 +53,7 @@
       InternalCompilerResult compilerResult,
       Class cls,
       Map<Id, ActualData<Features>> actualMap,
-      {bool verbose}) {
+      {bool? verbose}) {
     new ExtensionsDataExtractor(compilerResult, actualMap).computeForClass(cls);
   }
 
@@ -65,7 +63,7 @@
       InternalCompilerResult compilerResult,
       Library library,
       Map<Id, ActualData<Features>> actualMap,
-      {bool verbose}) {
+      {bool? verbose}) {
     new ExtensionsDataExtractor(compilerResult, actualMap)
         .computeForLibrary(library);
   }
@@ -76,7 +74,7 @@
       InternalCompilerResult compilerResult,
       Extension extension,
       Map<Id, ActualData<Features>> actualMap,
-      {bool verbose}) {
+      {bool? verbose}) {
     new ExtensionsDataExtractor(compilerResult, actualMap)
         .computeForExtension(extension);
   }
@@ -146,9 +144,9 @@
   Features computeLibraryValue(Id id, Library library) {
     Features features = new Features();
     SourceLibraryBuilder libraryBuilder =
-        lookupLibraryBuilder(compilerResult, library);
+        lookupLibraryBuilder(compilerResult, library) as SourceLibraryBuilder;
     libraryBuilder.forEachExtensionInScope((ExtensionBuilder extension) {
-      LibraryBuilder library = extension.parent;
+      LibraryBuilder library = extension.parent as LibraryBuilder;
       String libraryPrefix = '';
       if (library != libraryBuilder) {
         libraryPrefix = '${library.fileUri.pathSegments.last}.';
@@ -159,28 +157,32 @@
   }
 
   @override
-  Features computeClassValue(Id id, Class cls) {
-    ClassBuilder clsBuilder = lookupClassBuilder(compilerResult, cls);
+  Features? computeClassValue(Id id, Class cls) {
+    ClassBuilder clsBuilder =
+        lookupClassBuilder(compilerResult, cls) as ClassBuilder;
     if (!clsBuilder.isExtension) {
       return null;
     }
     Features features = new Features();
     features[Tags.builderName] = clsBuilder.name;
     if (clsBuilder.typeVariables != null) {
-      for (TypeVariableBuilder typeVariable in clsBuilder.typeVariables) {
+      for (TypeVariableBuilder typeVariable in clsBuilder.typeVariables!) {
         features.addElement(Tags.builderTypeParameters,
             typeVariableBuilderToText(typeVariable));
       }
     }
 
-    features[Tags.builderSupertype] = clsBuilder.supertypeBuilder?.name;
+    if (clsBuilder.supertypeBuilder != null) {
+      features[Tags.builderSupertype] =
+          clsBuilder.supertypeBuilder!.name as String;
+    }
     if (clsBuilder.interfaceBuilders != null) {
-      for (TypeBuilder superinterface in clsBuilder.interfaceBuilders) {
+      for (TypeBuilder superinterface in clsBuilder.interfaceBuilders!) {
         features.addElement(Tags.builderInterfaces, superinterface.name);
       }
     }
     if (clsBuilder.onTypes != null) {
-      for (TypeBuilder onType in clsBuilder.onTypes) {
+      for (TypeBuilder onType in clsBuilder.onTypes!) {
         features.addElement(Tags.builderOnTypes, typeBuilderToText(onType));
       }
     }
@@ -190,7 +192,9 @@
       features.addElement(
           Tags.clsTypeParameters, typeParameterToText(typeParameter));
     }
-    features[Tags.clsSupertype] = cls.supertype?.classNode?.name;
+    if (cls.supertype != null) {
+      features[Tags.clsSupertype] = cls.supertype!.classNode.name;
+    }
     for (Supertype superinterface in cls.implementedTypes) {
       features.addElement(Tags.clsInterfaces, superinterface.classNode.name);
     }
@@ -198,9 +202,9 @@
   }
 
   @override
-  Features computeExtensionValue(Id id, Extension extension) {
+  Features? computeExtensionValue(Id id, Extension extension) {
     ExtensionBuilder extensionBuilder =
-        lookupExtensionBuilder(compilerResult, extension);
+        lookupExtensionBuilder(compilerResult, extension)!;
     if (!extensionBuilder.isExtension) {
       return null;
     }
@@ -208,19 +212,14 @@
     features[Tags.builderName] = extensionBuilder.name;
     if (extensionBuilder.typeParameters != null) {
       for (TypeVariableBuilder typeVariable
-          in extensionBuilder.typeParameters) {
+          in extensionBuilder.typeParameters!) {
         features.addElement(Tags.builderTypeParameters,
             typeVariableBuilderToText(typeVariable));
       }
     }
-    if (extensionBuilder.onType != null) {
-      features[Tags.builderOnType] = typeBuilderToText(extensionBuilder.onType);
-    }
-
+    features[Tags.builderOnType] = typeBuilderToText(extensionBuilder.onType);
     features[Tags.extensionName] = extension.name;
-    if (extension.onType != null) {
-      features[Tags.extensionOnType] = typeToText(extension.onType);
-    }
+    features[Tags.extensionOnType] = typeToText(extension.onType);
     for (TypeParameter typeParameter in extension.typeParameters) {
       features.addElement(
           Tags.extensionTypeParameters, typeParameterToText(typeParameter));
@@ -233,17 +232,17 @@
   }
 
   @override
-  Features computeMemberValue(Id id, Member member) {
+  Features? computeMemberValue(Id id, Member member) {
     if (!member.isExtensionMember) {
       return null;
     }
 
-    MemberBuilder memberBuilder = lookupMemberBuilder(compilerResult, member);
+    MemberBuilder memberBuilder = lookupMemberBuilder(compilerResult, member)!;
     Features features = new Features();
     features[Tags.builderName] = memberBuilder.name;
     if (memberBuilder is FunctionBuilder) {
       if (memberBuilder.formals != null) {
-        for (FormalParameterBuilder parameter in memberBuilder.formals) {
+        for (FormalParameterBuilder parameter in memberBuilder.formals!) {
           if (parameter.isRequired) {
             features.addElement(Tags.builderRequiredParameters, parameter.name);
           } else if (parameter.isPositional) {
@@ -259,7 +258,7 @@
         features.markAsUnsorted(Tags.builderNamedParameters);
       }
       if (memberBuilder.typeVariables != null) {
-        for (TypeVariableBuilder typeVariable in memberBuilder.typeVariables) {
+        for (TypeVariableBuilder typeVariable in memberBuilder.typeVariables!) {
           features.addElement(Tags.builderTypeParameters,
               typeVariableBuilderToText(typeVariable));
         }
@@ -269,23 +268,23 @@
     features[Tags.memberName] = getMemberName(member);
     if (member.function != null) {
       for (int index = 0;
-          index < member.function.positionalParameters.length;
+          index < member.function!.positionalParameters.length;
           index++) {
         VariableDeclaration parameter =
-            member.function.positionalParameters[index];
-        if (index < member.function.requiredParameterCount) {
+            member.function!.positionalParameters[index];
+        if (index < member.function!.requiredParameterCount) {
           features.addElement(Tags.memberRequiredParameters, parameter.name);
         } else {
           features.addElement(Tags.memberPositionalParameters, parameter.name);
         }
       }
-      for (VariableDeclaration parameter in member.function.namedParameters) {
+      for (VariableDeclaration parameter in member.function!.namedParameters) {
         features.addElement(Tags.memberNamedParameters, parameter.name);
       }
       features.markAsUnsorted(Tags.memberRequiredParameters);
       features.markAsUnsorted(Tags.memberPositionalParameters);
       features.markAsUnsorted(Tags.memberNamedParameters);
-      for (TypeParameter typeParameter in member.function.typeParameters) {
+      for (TypeParameter typeParameter in member.function!.typeParameters) {
         features.addElement(
             Tags.memberTypeParameters, typeParameterToText(typeParameter));
       }
@@ -295,7 +294,7 @@
   }
 
   @override
-  Features computeNodeValue(Id id, TreeNode node) {
+  Features? computeNodeValue(Id id, TreeNode node) {
     if (node is ThisExpression) {
       Features features = new Features();
       features.add(Tags.hasThis);
diff --git a/pkg/front_end/test/fasta/ambiguous_export_test.dart b/pkg/front_end/test/fasta/ambiguous_export_test.dart
index fa0ef8f..2b9dc15 100644
--- a/pkg/front_end/test/fasta/ambiguous_export_test.dart
+++ b/pkg/front_end/test/fasta/ambiguous_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.md file.
 
-// @dart = 2.9
-
 import 'package:async_helper/async_helper.dart' show asyncTest;
 
 import 'package:expect/expect.dart' show Expect;
@@ -34,7 +32,7 @@
           await c.options.getUriTranslator(), c.options.target);
       target.loader.appendLibraries(component);
       DillLibraryBuilder builder = target.loader.read(library.importUri, -1);
-      await target.loader.buildOutline(builder);
+      target.loader.buildOutline(builder);
       builder.markAsReadyToFinalizeExports();
       var mainExport =
           builder.exportScope.lookupLocalMember("main", setter: false);
diff --git a/pkg/front_end/test/fasta/analyze_git_test.dart b/pkg/front_end/test/fasta/analyze_git_test.dart
index c649729..30d0ba3 100644
--- a/pkg/front_end/test/fasta/analyze_git_test.dart
+++ b/pkg/front_end/test/fasta/analyze_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 exitCode;
 
 import "package:testing/src/run_tests.dart" as testing show main;
diff --git a/pkg/front_end/test/fasta/analyze_src_with_lints_git_test.dart b/pkg/front_end/test/fasta/analyze_src_with_lints_git_test.dart
index b4b0e5f..664e888 100644
--- a/pkg/front_end/test/fasta/analyze_src_with_lints_git_test.dart
+++ b/pkg/front_end/test/fasta/analyze_src_with_lints_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 exitCode;
 
 import "package:testing/src/run_tests.dart" as testing show main;
diff --git a/pkg/front_end/test/fasta/assert_locations_test.dart b/pkg/front_end/test/fasta/assert_locations_test.dart
index 029196a..4f4787e 100644
--- a/pkg/front_end/test/fasta/assert_locations_test.dart
+++ b/pkg/front_end/test/fasta/assert_locations_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.assert_locations_test;
 
 import 'package:async_helper/async_helper.dart' show asyncTest;
@@ -47,7 +45,7 @@
   // parenthesis of the assert statement to the first character of the
   // condition.
   void makeAssertWithMessage(String condition,
-      {String message, bool trailingComma: false, int additionalOffset: 0}) {
+      {String? message, bool trailingComma: false, int additionalOffset: 0}) {
     final name = 'testCase${spans.length}';
     sb.writeln('void $name(x) {');
     sb.write('assert(');
@@ -110,7 +108,7 @@
 
   /// When [AssertStatement] is reached it is checked against this
   /// span.
-  ConditionSpan expectedSpan;
+  ConditionSpan? expectedSpan;
 
   VerifyingVisitor(this.test);
 
@@ -126,8 +124,8 @@
 
   @override
   void visitAssertStatement(AssertStatement node) {
-    Expect.equals(expectedSpan.startOffset, node.conditionStartOffset);
-    Expect.equals(expectedSpan.endOffset, node.conditionEndOffset);
+    Expect.equals(expectedSpan!.startOffset, node.conditionStartOffset);
+    Expect.equals(expectedSpan!.endOffset, node.conditionEndOffset);
   }
 }
 
@@ -139,12 +137,12 @@
         Expect.fail(
             "Unexpected message: ${message.plainTextFormatted.join('\n')}");
       };
-    Component p = (await compileScript(test.source,
+    Component? p = (await compileScript(test.source,
             options: options, fileName: 'synthetic-test.dart'))
         ?.component;
     Expect.isNotNull(p);
     VerifyingVisitor visitor = new VerifyingVisitor(test);
-    p.mainMethod.enclosingLibrary.accept(visitor);
+    p!.mainMethod!.enclosingLibrary.accept(visitor);
     Expect.setEquals(test.spans.keys, visitor.verified);
   });
 }
diff --git a/pkg/front_end/test/fasta/bootstrap_test.dart b/pkg/front_end/test/fasta/bootstrap_test.dart
index fd49f77..750c685 100644
--- a/pkg/front_end/test/fasta/bootstrap_test.dart
+++ b/pkg/front_end/test/fasta/bootstrap_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;
 
 import 'package:async_helper/async_helper.dart' show asyncEnd, asyncStart;
diff --git a/pkg/front_end/test/fasta/expression_suite.dart b/pkg/front_end/test/fasta/expression_suite.dart
index d6159f9..bc3fb8e 100644
--- a/pkg/front_end/test/fasta/expression_suite.dart
+++ b/pkg/front_end/test/fasta/expression_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.md file.
 
-// @dart = 2.9
-
 library fasta.test.expression_test;
 
 import "dart:convert" show JsonEncoder;
@@ -73,7 +71,7 @@
 
   ProcessedOptions get options => compilerContext.options;
 
-  MemoryFileSystem get fileSystem => options.fileSystem;
+  MemoryFileSystem get fileSystem => options.fileSystem as MemoryFileSystem;
 
   Future<T> runInContext<T>(Future<T> action(CompilerContext c)) {
     return compilerContext.runInContext<T>(action);
@@ -91,7 +89,7 @@
 }
 
 class CompilationResult {
-  Procedure compiledProcedure;
+  Procedure? compiledProcedure;
   List<DiagnosticMessage> errors;
   CompilationResult(this.compiledProcedure, this.errors);
 
@@ -115,7 +113,7 @@
       buffer.write("<no procedure>");
     } else {
       Printer printer = new Printer(buffer);
-      printer.visitProcedure(compiledProcedure);
+      printer.visitProcedure(compiledProcedure!);
       printer.writeConstantTable(new Component());
     }
     Uri base = entryPoint.resolve(".");
@@ -126,9 +124,9 @@
 class TestCase {
   final TestDescription description;
 
-  final Uri entryPoint;
+  final Uri? entryPoint;
 
-  final Uri import;
+  final Uri? import;
 
   final List<String> definitions;
 
@@ -136,13 +134,13 @@
 
   final bool isStaticMethod;
 
-  final Uri library;
+  final Uri? library;
 
-  final String className;
+  final String? className;
 
-  final String methodName;
+  final String? methodName;
 
-  String expression;
+  String? expression;
 
   List<CompilationResult> results = [];
 
@@ -170,12 +168,12 @@
         "static = $isStaticMethod)";
   }
 
-  String validate() {
+  String? validate() {
     print(this);
     if (entryPoint == null) {
       return "No entryPoint.";
     }
-    if (!(new File.fromUri(entryPoint)).existsSync()) {
+    if (!(new File.fromUri(entryPoint!)).existsSync()) {
       return "Entry point $entryPoint doesn't exist.";
     }
     if (library == null) {
@@ -202,10 +200,10 @@
   Future<Result<Null>> run(List<TestCase> tests, Context context) async {
     String actual = "";
     for (var test in tests) {
-      var primary = test.results.first.printResult(test.entryPoint, context);
+      var primary = test.results.first.printResult(test.entryPoint!, context);
       actual += primary;
       for (int i = 1; i < test.results.length; ++i) {
-        var secondary = test.results[i].printResult(test.entryPoint, context);
+        var secondary = test.results[i].printResult(test.entryPoint!, context);
         if (primary != secondary) {
           return fail(
               null,
@@ -256,15 +254,15 @@
     Uri uri = description.uri;
     String contents = await new File.fromUri(uri).readAsString();
 
-    Uri entryPoint;
-    Uri import;
+    Uri? entryPoint;
+    Uri? import;
     List<String> definitions = <String>[];
     List<String> typeDefinitions = <String>[];
     bool isStaticMethod = false;
-    Uri library;
-    String className;
-    String methodName;
-    String expression;
+    Uri? library;
+    String? className;
+    String? methodName;
+    String? expression;
 
     dynamic maps = loadYamlNode(contents, sourceUrl: uri);
     if (maps is YamlMap) maps = [maps];
@@ -282,7 +280,7 @@
         } else if (key == "position") {
           Uri uri = description.uri.resolveUri(Uri.parse(value as String));
           library = uri.removeFragment();
-          if (uri.fragment != null && uri.fragment != '') {
+          if (uri.fragment != '') {
             className = uri.fragment;
           }
         } else if (key == "method") {
@@ -339,12 +337,12 @@
           .add(new TypeParameter(name, new DynamicType(), new DynamicType()));
     }
 
-    Procedure compiledProcedure = await compiler.compileExpression(
-      test.expression,
+    Procedure? compiledProcedure = await compiler.compileExpression(
+      test.expression!,
       definitions,
       typeParams,
       "debugExpr",
-      test.library,
+      test.library!,
       className: test.className,
       methodName: test.methodName,
       isStatic: test.isStaticMethod,
@@ -363,23 +361,24 @@
   Future<Result<List<TestCase>>> run(
       List<TestCase> tests, Context context) async {
     for (var test in tests) {
-      context.fileSystem.entityForUri(test.entryPoint).writeAsBytesSync(
-          await new File.fromUri(test.entryPoint).readAsBytes());
+      context.fileSystem.entityForUri(test.entryPoint!).writeAsBytesSync(
+          await new File.fromUri(test.entryPoint!).readAsBytes());
 
       if (test.import != null) {
-        context.fileSystem.entityForUri(test.import).writeAsBytesSync(
-            await new File.fromUri(test.import).readAsBytes());
+        context.fileSystem.entityForUri(test.import!).writeAsBytesSync(
+            await new File.fromUri(test.import!).readAsBytes());
       }
 
       var sourceCompiler = new IncrementalCompiler(context.compilerContext);
       Component component =
-          await sourceCompiler.computeDelta(entryPoints: [test.entryPoint]);
+          await sourceCompiler.computeDelta(entryPoints: [test.entryPoint!]);
       var errors = context.takeErrors();
       if (!errors.isEmpty) {
         return fail(tests, "Couldn't compile entry-point: $errors");
       }
       Uri dillFileUri = new Uri(
-          scheme: test.entryPoint.scheme, path: test.entryPoint.path + ".dill");
+          scheme: test.entryPoint!.scheme,
+          path: test.entryPoint!.path + ".dill");
       File dillFile = new File.fromUri(dillFileUri);
       if (!await dillFile.exists()) {
         await writeComponentToFile(component, dillFileUri);
@@ -391,7 +390,7 @@
       var dillCompiler =
           new IncrementalCompiler(context.compilerContext, dillFileUri);
       component =
-          await dillCompiler.computeDelta(entryPoints: [test.entryPoint]);
+          await dillCompiler.computeDelta(entryPoints: [test.entryPoint!]);
       component.computeCanonicalNames();
       await dillFile.delete();
 
diff --git a/pkg/front_end/test/fasta/generator_to_string_test.dart b/pkg/front_end/test/fasta/generator_to_string_test.dart
index b2c4f52..5fdfef0 100644
--- a/pkg/front_end/test/fasta/generator_to_string_test.dart
+++ b/pkg/front_end/test/fasta/generator_to_string_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 of toString on generators.
 
 import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
@@ -32,7 +30,8 @@
         VariableDeclaration,
         VariableGet,
         VoidType,
-        defaultLanguageVersion;
+        defaultLanguageVersion,
+        dummyLibraryDependency;
 import 'package:kernel/class_hierarchy.dart';
 import 'package:kernel/core_types.dart';
 
@@ -61,6 +60,8 @@
 import 'package:front_end/src/fasta/source/source_library_builder.dart'
     show ImplicitLanguageVersion, SourceLibraryBuilder;
 
+import '../mock_file_system.dart';
+
 void check(String expected, Object generator) {
   Expect.stringEquals(expected, "$generator");
 }
@@ -88,7 +89,7 @@
         /*packageUri*/ null,
         new ImplicitLanguageVersion(defaultLanguageVersion),
         new KernelTarget(
-                null,
+                const MockFileSystem(),
                 false,
                 new DillTarget(c.options.ticker, uriTranslator,
                     new NoneTarget(new TargetFlags())),
@@ -97,7 +98,7 @@
         null);
     libraryBuilder.markLanguageVersionFinal();
     LoadLibraryBuilder loadLibraryBuilder =
-        new LoadLibraryBuilder(libraryBuilder, null, -1);
+        new LoadLibraryBuilder(libraryBuilder, dummyLibraryDependency, -1);
     Procedure getter = new Procedure(
         new Name("myGetter"), ProcedureKind.Getter, new FunctionNode(null),
         fileUri: uri);
diff --git a/pkg/front_end/test/fasta/incremental_dartino_suite.dart b/pkg/front_end/test/fasta/incremental_dartino_suite.dart
index f3b354e..2f7c9f6 100644
--- a/pkg/front_end/test/fasta/incremental_dartino_suite.dart
+++ b/pkg/front_end/test/fasta/incremental_dartino_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.md file.
 
-// @dart = 2.9
-
 library fasta.test.incremental_test;
 
 import "dart:convert" show JsonEncoder;
@@ -73,7 +71,7 @@
 
   ProcessedOptions get options => compilerContext.options;
 
-  MemoryFileSystem get fileSystem => options.fileSystem;
+  MemoryFileSystem get fileSystem => options.fileSystem as MemoryFileSystem;
 
   Future<T> runInContext<T>(Future<T> action(CompilerContext c)) {
     return compilerContext.runInContext<T>(action);
@@ -103,9 +101,9 @@
     Uri uri = description.uri;
     String contents = await new File.fromUri(uri).readAsString();
     Map<String, List<String>> sources = <String, List<String>>{};
-    List<IncrementalExpectation> expectations;
+    List<IncrementalExpectation>? expectations;
     bool firstPatch = true;
-    YamlMap map = loadYamlNode(contents, sourceUrl: uri);
+    YamlMap map = loadYamlNode(contents, sourceUrl: uri) as YamlMap;
     map.forEach((_fileName, _contents) {
       String fileName = _fileName; // Strong mode hurray!
       String contents = _contents; // Strong mode hurray!
@@ -135,7 +133,7 @@
   Future<Result<TestCase>> run(TestCase test, Context context) async {
     for (int edits = 0;; edits++) {
       bool foundSources = false;
-      test.sources.forEach((String name, List<String> sources) {
+      test.sources!.forEach((String name, List<String> sources) {
         if (edits < sources.length) {
           String source = sources[edits];
           Uri uri = base.resolve(name);
@@ -157,7 +155,7 @@
       Component component =
           await compiler.computeDelta(entryPoints: [entryPoint]);
       List<DiagnosticMessage> errors = context.takeErrors();
-      if (test.expectations[edits].hasCompileTimeError) {
+      if (test.expectations![edits].hasCompileTimeError) {
         if (errors.isEmpty) {
           return fail(test, "Compile-time error expected, but none reported");
         }
@@ -176,9 +174,9 @@
 class TestCase {
   final TestDescription description;
 
-  final Map<String, List<String>> sources;
+  final Map<String, List<String>>? sources;
 
-  final List<IncrementalExpectation> expectations;
+  final List<IncrementalExpectation>? expectations;
 
   TestCase(this.description, this.sources, this.expectations);
 
@@ -192,16 +190,16 @@
     if (sources == null) {
       return step.fail(this, "No sources.");
     }
-    if (expectations == null || expectations.isEmpty) {
+    if (expectations == null || expectations!.isEmpty) {
       return step.fail(this, "No expectations.");
     }
-    for (String name in sources.keys) {
-      List<String> versions = sources[name];
-      if (versions.length != 1 && versions.length != expectations.length) {
+    for (String name in sources!.keys) {
+      List<String> versions = sources![name]!;
+      if (versions.length != 1 && versions.length != expectations!.length) {
         return step.fail(
             this,
             "Found ${versions.length} versions of $name,"
-            " but expected 1 or ${expectations.length}.");
+            " but expected 1 or ${expectations!.length}.");
       }
     }
     return step.pass(this);
diff --git a/pkg/front_end/test/fasta/incremental_expectations.dart b/pkg/front_end/test/fasta/incremental_expectations.dart
index 7e9a45c..1bf173d 100644
--- a/pkg/front_end/test/fasta/incremental_expectations.dart
+++ b/pkg/front_end/test/fasta/incremental_expectations.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
-
 library fasta.test.incremental_expectations;
 
 import "dart:convert" show JsonDecoder, JsonEncoder;
diff --git a/pkg/front_end/test/fasta/incremental_hello_test.dart b/pkg/front_end/test/fasta/incremental_hello_test.dart
index 33d7d3a..ea8e04c 100644
--- a/pkg/front_end/test/fasta/incremental_hello_test.dart
+++ b/pkg/front_end/test/fasta/incremental_hello_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:async_helper/async_helper.dart' show asyncTest;
@@ -34,7 +32,7 @@
   throw "Unexpected message: ${message.plainTextFormatted.join('\n')}";
 }
 
-Future<void> test({bool sdkFromSource}) async {
+Future<void> test({required bool sdkFromSource}) async {
   final CompilerOptions optionBuilder = new CompilerOptions()
     ..packagesFileUri = Uri.base.resolve(".packages")
     ..target = new VmTarget(new TargetFlags())
diff --git a/pkg/front_end/test/fasta/incremental_source_files.dart b/pkg/front_end/test/fasta/incremental_source_files.dart
index 4108b62..274e65b 100644
--- a/pkg/front_end/test/fasta/incremental_source_files.dart
+++ b/pkg/front_end/test/fasta/incremental_source_files.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_source_files;
 
 /// Expand a file with diffs in common merge conflict format into a [List] that
@@ -57,10 +55,9 @@
 ///   ["head v1 tail", "head v2 tail"]
 List<String> expandUpdates(List updates) {
   int outputCount = updates.firstWhere((e) => e is Iterable).length;
-  List<StringBuffer> result = new List<StringBuffer>.filled(outputCount, null);
-  for (int i = 0; i < outputCount; i++) {
-    result[i] = new StringBuffer();
-  }
+  List<StringBuffer> result = new List<StringBuffer>.generate(
+      outputCount, (_) => new StringBuffer(),
+      growable: false);
   for (var chunk in updates) {
     if (chunk is Iterable) {
       int segmentCount = 0;
diff --git a/pkg/front_end/test/fasta/link_test.dart b/pkg/front_end/test/fasta/link_test.dart
index d1029eb..7918446 100644
--- a/pkg/front_end/test/fasta/link_test.dart
+++ b/pkg/front_end/test/fasta/link_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/util/link.dart' show Link, LinkBuilder;
 
 import 'package:expect/expect.dart' show Expect;
@@ -45,6 +43,6 @@
   Expect.stringEquals("[ B, A ]", "${strings.reverse(const Link<String>())}");
 
   Link<int> ints =
-      const Link<int>().prepend(1).reverse(const Link<int>()).tail.prepend(1);
+      const Link<int>().prepend(1).reverse(const Link<int>()).tail!.prepend(1);
   Expect.stringEquals("[ 1 ]", "${ints}");
 }
diff --git a/pkg/front_end/test/mock_file_system.dart b/pkg/front_end/test/mock_file_system.dart
new file mode 100644
index 0000000..60e45be
--- /dev/null
+++ b/pkg/front_end/test/mock_file_system.dart
@@ -0,0 +1,27 @@
+// 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:front_end/src/api_prototype/file_system.dart';
+
+class MockFileSystem implements FileSystem {
+  final String? scheme;
+
+  const MockFileSystem({this.scheme});
+
+  @override
+  FileSystemEntity entityForUri(Uri uri) {
+    if (scheme != null && uri.scheme != scheme) throw "unsupported";
+    return new MockFileSystemEntity(uri, this);
+  }
+}
+
+class MockFileSystemEntity implements FileSystemEntity {
+  @override
+  final Uri uri;
+  final FileSystem fileSystem;
+  MockFileSystemEntity(this.uri, this.fileSystem);
+
+  @override
+  dynamic noSuchMethod(m) => super.noSuchMethod(m);
+}
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 3be6daf..4332fca 100644
--- a/pkg/front_end/test/scheme_based_file_system_test.dart
+++ b/pkg/front_end/test/scheme_based_file_system_test.dart
@@ -7,10 +7,12 @@
 
 import 'package:test/test.dart';
 
+import 'mock_file_system.dart';
+
 void main() {
   test('lookup of registered schemes is handled', () {
-    var fs1 = new MockFileSystem('scheme1');
-    var fs2 = new MockFileSystem('scheme2');
+    var fs1 = new MockFileSystem(scheme: 'scheme1');
+    var fs2 = new MockFileSystem(scheme: 'scheme2');
     var fileSystem =
         new SchemeBasedFileSystem({'scheme1': fs1, 'scheme2': fs2});
 
@@ -23,30 +25,9 @@
   });
 
   test('lookup of an unregistered scheme will throw', () {
-    var fileSystem =
-        new SchemeBasedFileSystem({'scheme1': new MockFileSystem('scheme1')});
+    var fileSystem = new SchemeBasedFileSystem(
+        {'scheme1': new MockFileSystem(scheme: 'scheme1')});
     expect(() => fileSystem.entityForUri(Uri.parse('scheme2:a.dart')),
         throwsA((e) => e is FileSystemException));
   });
 }
-
-class MockFileSystem implements FileSystem {
-  String scheme;
-  MockFileSystem(this.scheme);
-
-  @override
-  FileSystemEntity entityForUri(Uri uri) {
-    if (uri.scheme != scheme) throw "unsupported";
-    return new MockFileSystemEntity(uri, this);
-  }
-}
-
-class MockFileSystemEntity implements FileSystemEntity {
-  @override
-  final Uri uri;
-  final FileSystem fileSystem;
-  MockFileSystemEntity(this.uri, this.fileSystem);
-
-  @override
-  dynamic noSuchMethod(m) => super.noSuchMethod(m);
-}
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index 4e7efb6..3c8bb1f 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -237,6 +237,10 @@
     return cid_start == kIllegalCid && cid_end == kIllegalCid;
   }
 
+  bool Equals(const CidRangeValue& other) const {
+    return cid_start == other.cid_start && cid_end == other.cid_end;
+  }
+
   intptr_t cid_start;
   intptr_t cid_end;
 };
@@ -959,6 +963,11 @@
     return static_cast<T*>(this);
   }
 
+  template <typename T>
+  const T* Cast() const {
+    return static_cast<const T*>(this);
+  }
+
   // Returns structure describing location constraints required
   // to emit native code for this instruction.
   LocationSummary* locs() {
@@ -8893,7 +8902,9 @@
   virtual bool AllowsCSE() const { return true; }
   virtual bool HasUnknownSideEffects() const { return false; }
 
-  virtual bool AttributesEqual(const Instruction& other) const { return true; }
+  virtual bool AttributesEqual(const Instruction& other) const {
+    return other.Cast<CheckClassIdInstr>()->cids().Equals(cids_);
+  }
 
   PRINT_OPERANDS_TO_SUPPORT
 
diff --git a/runtime/vm/compiler/backend/redundancy_elimination.cc b/runtime/vm/compiler/backend/redundancy_elimination.cc
index 67f8112..e356875 100644
--- a/runtime/vm/compiler/backend/redundancy_elimination.cc
+++ b/runtime/vm/compiler/backend/redundancy_elimination.cc
@@ -29,10 +29,15 @@
 // Quick access to the current zone.
 #define Z (zone())
 
-class CSEInstructionMap : public ValueObject {
+// A set of Instructions used by CSE pass.
+//
+// Instructions are compared as if all redefinitions were removed from the
+// graph, with the exception of LoadField instruction which gets special
+// treatment.
+class CSEInstructionSet : public ValueObject {
  public:
-  CSEInstructionMap() : map_() {}
-  explicit CSEInstructionMap(const CSEInstructionMap& other)
+  CSEInstructionSet() : map_() {}
+  explicit CSEInstructionSet(const CSEInstructionSet& other)
       : ValueObject(), map_(other.map_) {}
 
   Instruction* Lookup(Instruction* other) const {
@@ -46,7 +51,59 @@
   }
 
  private:
-  PointerSet<Instruction> map_;
+  static Definition* OriginalDefinition(Value* value) {
+    return value->definition()->OriginalDefinition();
+  }
+
+  static bool EqualsIgnoringRedefinitions(const Instruction& a,
+                                          const Instruction& b) {
+    const auto tag = a.tag();
+    if (tag != b.tag()) return false;
+    const auto input_count = a.InputCount();
+    if (input_count != b.InputCount()) return false;
+
+    // We would like to avoid replacing a load from a redefinition with a
+    // load from an original definition because that breaks the dependency
+    // on the redefinition and enables potentially incorrect code motion.
+    if (tag != Instruction::kLoadField) {
+      for (intptr_t i = 0; i < input_count; ++i) {
+        if (OriginalDefinition(a.InputAt(i)) !=
+            OriginalDefinition(b.InputAt(i))) {
+          return false;
+        }
+      }
+    } else {
+      for (intptr_t i = 0; i < input_count; ++i) {
+        if (!a.InputAt(i)->Equals(*b.InputAt(i))) return false;
+      }
+    }
+    return a.AttributesEqual(b);
+  }
+
+  class Trait {
+   public:
+    typedef Instruction* Value;
+    typedef Instruction* Key;
+    typedef Instruction* Pair;
+
+    static Key KeyOf(Pair kv) { return kv; }
+    static Value ValueOf(Pair kv) { return kv; }
+
+    static inline uword Hash(Key key) {
+      uword result = key->tag();
+      for (intptr_t i = 0; i < key->InputCount(); ++i) {
+        result = CombineHashes(
+            result, OriginalDefinition(key->InputAt(i))->ssa_temp_index());
+      }
+      return FinalizeHash(result, kBitsPerInt32 - 1);
+    }
+
+    static inline bool IsKeyEqual(Pair kv, Key key) {
+      return EqualsIgnoringRedefinitions(*kv, *key);
+    }
+  };
+
+  DirectChainedHashMap<Trait> map_;
 };
 
 // Place describes an abstract location (e.g. field) that IR can load
@@ -2870,13 +2927,14 @@
   DISALLOW_COPY_AND_ASSIGN(LoadOptimizer);
 };
 
-bool DominatorBasedCSE::Optimize(FlowGraph* graph) {
+bool DominatorBasedCSE::Optimize(FlowGraph* graph,
+                                 bool run_load_optimization /* = true */) {
   bool changed = false;
-  if (FLAG_load_cse) {
+  if (FLAG_load_cse && run_load_optimization) {
     changed = LoadOptimizer::OptimizeGraph(graph) || changed;
   }
 
-  CSEInstructionMap map;
+  CSEInstructionSet map;
   changed = OptimizeRecursive(graph, graph->graph_entry(), &map) || changed;
 
   return changed;
@@ -2884,7 +2942,7 @@
 
 bool DominatorBasedCSE::OptimizeRecursive(FlowGraph* graph,
                                           BlockEntryInstr* block,
-                                          CSEInstructionMap* map) {
+                                          CSEInstructionSet* map) {
   bool changed = false;
   for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
     Instruction* current = it.Current();
@@ -2912,7 +2970,7 @@
     BlockEntryInstr* child = block->dominated_blocks()[i];
     if (i < num_children - 1) {
       // Copy map.
-      CSEInstructionMap child_map(*map);
+      CSEInstructionSet child_map(*map);
       changed = OptimizeRecursive(graph, child, &child_map) || changed;
     } else {
       // Reuse map for the last child.
diff --git a/runtime/vm/compiler/backend/redundancy_elimination.h b/runtime/vm/compiler/backend/redundancy_elimination.h
index 877223b..6042147 100644
--- a/runtime/vm/compiler/backend/redundancy_elimination.h
+++ b/runtime/vm/compiler/backend/redundancy_elimination.h
@@ -14,7 +14,7 @@
 
 namespace dart {
 
-class CSEInstructionMap;
+class CSEInstructionSet;
 
 class AllocationSinking : public ZoneAllocated {
  public:
@@ -88,12 +88,12 @@
  public:
   // Return true, if the optimization changed the flow graph.
   // False, if nothing changed.
-  static bool Optimize(FlowGraph* graph);
+  static bool Optimize(FlowGraph* graph, bool run_load_optimization = true);
 
  private:
   static bool OptimizeRecursive(FlowGraph* graph,
                                 BlockEntryInstr* entry,
-                                CSEInstructionMap* map);
+                                CSEInstructionSet* map);
 };
 
 class DeadStoreElimination : public AllStatic {
diff --git a/runtime/vm/compiler/backend/redundancy_elimination_test.cc b/runtime/vm/compiler/backend/redundancy_elimination_test.cc
index 353fdd7..1dd76e6 100644
--- a/runtime/vm/compiler/backend/redundancy_elimination_test.cc
+++ b/runtime/vm/compiler/backend/redundancy_elimination_test.cc
@@ -1352,6 +1352,116 @@
   }
 }
 
+// This test checks that CSE unwraps redefinitions when comparing all
+// instructions except loads, which are handled specially.
+ISOLATE_UNIT_TEST_CASE(CSE_Redefinitions) {
+  const char* script_chars = R"(
+    @pragma("vm:external-name", "BlackholeNative")
+    external dynamic blackhole([a, b, c, d, e, f]);
+    class K<T> {
+      final T field;
+      K(this.field);
+    }
+  )";
+  const Library& lib =
+      Library::Handle(LoadTestScript(script_chars, NoopNativeLookup));
+
+  const Class& cls = Class::ZoneHandle(
+      lib.LookupLocalClass(String::Handle(Symbols::New(thread, "K"))));
+  const Error& err = Error::Handle(cls.EnsureIsFinalized(thread));
+  EXPECT(err.IsNull());
+
+  const Field& original_field = Field::Handle(
+      cls.LookupField(String::Handle(Symbols::New(thread, "field"))));
+  EXPECT(!original_field.IsNull());
+  const Field& field = Field::Handle(original_field.CloneFromOriginal());
+
+  const Function& blackhole =
+      Function::ZoneHandle(GetFunction(lib, "blackhole"));
+
+  using compiler::BlockBuilder;
+  CompilerState S(thread, /*is_aot=*/false, /*is_optimizing=*/true);
+  FlowGraphBuilderHelper H;
+
+  auto b1 = H.flow_graph()->graph_entry()->normal_entry();
+
+  BoxInstr* box0;
+  BoxInstr* box1;
+  LoadFieldInstr* load0;
+  LoadFieldInstr* load1;
+  LoadFieldInstr* load2;
+  StaticCallInstr* call;
+  ReturnInstr* ret;
+
+  {
+    BlockBuilder builder(H.flow_graph(), b1);
+    auto& slot = Slot::Get(field, &H.flow_graph()->parsed_function());
+    auto param0 =
+        builder.AddParameter(0, 0, /*with_frame=*/true, kUnboxedDouble);
+    auto param1 = builder.AddParameter(1, 2, /*with_frame=*/true, kTagged);
+    auto redef0 =
+        builder.AddDefinition(new RedefinitionInstr(new Value(param0)));
+    auto redef1 =
+        builder.AddDefinition(new RedefinitionInstr(new Value(param0)));
+    box0 = builder.AddDefinition(
+        BoxInstr::Create(kUnboxedDouble, new Value(redef0)));
+    box1 = builder.AddDefinition(
+        BoxInstr::Create(kUnboxedDouble, new Value(redef1)));
+
+    auto redef2 =
+        builder.AddDefinition(new RedefinitionInstr(new Value(param1)));
+    auto redef3 =
+        builder.AddDefinition(new RedefinitionInstr(new Value(param1)));
+    load0 = builder.AddDefinition(
+        new LoadFieldInstr(new Value(redef2), slot, InstructionSource()));
+    load1 = builder.AddDefinition(
+        new LoadFieldInstr(new Value(redef3), slot, InstructionSource()));
+    load2 = builder.AddDefinition(
+        new LoadFieldInstr(new Value(redef3), slot, InstructionSource()));
+
+    auto args = new InputsArray(3);
+    args->Add(new Value(load0));
+    args->Add(new Value(load1));
+    args->Add(new Value(load2));
+    call = builder.AddInstruction(new StaticCallInstr(
+        InstructionSource(), blackhole, 0, Array::empty_array(), args,
+        S.GetNextDeoptId(), 0, ICData::RebindRule::kStatic));
+
+    ret = builder.AddReturn(new Value(box1));
+  }
+  H.FinishGraph();
+
+  // Running CSE without load optimization should eliminate redundant boxing
+  // but keep loads intact if they don't  have exactly matching inputs.
+  DominatorBasedCSE::Optimize(H.flow_graph(), /*run_load_optimization=*/false);
+
+  EXPECT_PROPERTY(box1, it.WasEliminated());
+  EXPECT_PROPERTY(ret, it.value()->definition() == box0);
+
+  EXPECT_PROPERTY(load0, !it.WasEliminated());
+  EXPECT_PROPERTY(load1, !it.WasEliminated());
+  EXPECT_PROPERTY(load2, it.WasEliminated());
+
+  EXPECT_PROPERTY(call, it.ArgumentAt(0) == load0);
+  EXPECT_PROPERTY(call, it.ArgumentAt(1) == load1);
+  EXPECT_PROPERTY(call, it.ArgumentAt(2) == load1);
+
+  // Running load optimization pass should remove the second load but
+  // insert a redefinition to prevent code motion because the field
+  // has a generic type.
+  DominatorBasedCSE::Optimize(H.flow_graph(), /*run_load_optimization=*/true);
+
+  EXPECT_PROPERTY(load0, !it.WasEliminated());
+  EXPECT_PROPERTY(load1, it.WasEliminated());
+  EXPECT_PROPERTY(load2, it.WasEliminated());
+
+  EXPECT_PROPERTY(call, it.ArgumentAt(0) == load0);
+  EXPECT_PROPERTY(call, it.ArgumentAt(1)->IsRedefinition() &&
+                            it.ArgumentAt(1)->OriginalDefinition() == load0);
+  EXPECT_PROPERTY(call, it.ArgumentAt(2)->IsRedefinition() &&
+                            it.ArgumentAt(2)->OriginalDefinition() == load0);
+}
+
 #endif  // !defined(TARGET_ARCH_IA32)
 
 }  // namespace dart
diff --git a/runtime/vm/hash_map.h b/runtime/vm/hash_map.h
index 2c816a6..aacbf87 100644
--- a/runtime/vm/hash_map.h
+++ b/runtime/vm/hash_map.h
@@ -310,7 +310,7 @@
             ASSERT_NOTNULL(zone),
             initial_size) {}
 
-  // There is a current use of the copy constructor in CSEInstructionMap
+  // There is a current use of the copy constructor in CSEInstructionSet
   // (compiler/backend/redundancy_elimination.cc), so work is needed if we
   // want to disallow it.
   DirectChainedHashMap(const DirectChainedHashMap& other)
diff --git a/tools/VERSION b/tools/VERSION
index c188fa2..7a5b83a 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 16
 PATCH 0
-PRERELEASE 17
+PRERELEASE 18
 PRERELEASE_PATCH 0
\ No newline at end of file