Version 2.12.0-56.0.dev

Merge commit '3c2a5c8976dbe6516a5202a676cabdd209ac35a5' into 'dev'
diff --git a/DEPS b/DEPS
index 5621e3d..c3a0f78 100644
--- a/DEPS
+++ b/DEPS
@@ -132,7 +132,7 @@
   "ply_rev": "604b32590ffad5cbb82e4afef1d305512d06ae93",
   "pool_rev": "eedbd5fde84f9a1a8da643b475305a81841da599",
   "protobuf_rev": "3746c8fd3f2b0147623a8e3db89c3ff4330de760",
-  "pub_rev": "c00d4b4abf5b4ff265a7ce6282b748551f1b5b1f",
+  "pub_rev": "228e69e53862879c283c42b98086aa7536012a66",
   "pub_semver_tag": "v1.4.4",
   "resource_rev": "6b79867d0becf5395e5819a75720963b8298e9a7",
   "root_certificates_rev": "7e5ec82c99677a2e5b95ce296c4d68b0d3378ed8",
diff --git a/pkg/analysis_server/doc/api.html b/pkg/analysis_server/doc/api.html
index b5db21c..76a9bda 100644
--- a/pkg/analysis_server/doc/api.html
+++ b/pkg/analysis_server/doc/api.html
@@ -109,7 +109,7 @@
 <body>
 <h1>Analysis Server API Specification</h1>
 <h1 style="color:#999999">Version
-  1.31.0
+  1.32.0
 </h1>
 <p>
   This document contains a specification of the API provided by the
@@ -3046,6 +3046,8 @@
   
   
   
+  
+  
 <dl><dt class="typeDefinition"><a name="type_AddContentOverlay">AddContentOverlay: object</a></dt><dd>
     <p>
       A directive to begin overlaying the contents of a file. The supplied
@@ -3323,6 +3325,37 @@
         </p>
       </dd><dt class="field"><b>items: List&lt;<a href="#type_AvailableSuggestion">AvailableSuggestion</a>&gt;</b></dt><dd>
         
+      </dd></dl></dd><dt class="typeDefinition"><a name="type_BulkFix">BulkFix: object</a></dt><dd>
+    <p>
+      A description of bulk fixes to a library.
+    </p>
+    
+  <dl><dt class="field"><b>path: <a href="#type_FilePath">FilePath</a></b></dt><dd>
+        
+        <p>
+          The path of the library.
+        </p>
+      </dd><dt class="field"><b>fixes: List&lt;<a href="#type_BulkFixDetail">BulkFixDetail</a>&gt;</b></dt><dd>
+        
+        <p>
+          A list of bulk fix details.
+        </p>
+      </dd></dl></dd><dt class="typeDefinition"><a name="type_BulkFixDetail">BulkFixDetail: object</a></dt><dd>
+    <p>
+      A description of a fix applied to a library.
+    </p>
+    
+  <dl><dt class="field"><b>code: String</b></dt><dd>
+        
+        <p>
+          The code of the diagnostic associated with the fix.
+        </p>
+      </dd><dt class="field"><b>occurrences: int</b></dt><dd>
+        
+        <p>
+          The number times the associated diagnostic was fixed in the associated
+          source edit.
+        </p>
       </dd></dl></dd><dt class="typeDefinition"><a name="type_ChangeContentOverlay">ChangeContentOverlay: object</a></dt><dd>
     <p>
       A directive to modify an existing file content overlay. One or more ranges
@@ -5957,7 +5990,7 @@
   TODO: TBD
 </p>
 <h2 class="domain"><a name="index">Index</a></h2>
-<h3>Domains</h3><h4>server (<a href="#domain_server">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_server.getVersion">getVersion</a></li><li><a href="#request_server.shutdown">shutdown</a></li><li><a href="#request_server.setSubscriptions">setSubscriptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_server.connected">connected</a></li><li><a href="#notification_server.error">error</a></li><li><a href="#notification_server.log">log</a></li><li><a href="#notification_server.status">status</a></li></ul></div></div><h4>analysis (<a href="#domain_analysis">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_analysis.getErrors">getErrors</a></li><li><a href="#request_analysis.getHover">getHover</a></li><li><a href="#request_analysis.getLibraryDependencies">getLibraryDependencies</a></li><li><a href="#request_analysis.getNavigation">getNavigation</a></li><li><a href="#request_analysis.getReachableSources">getReachableSources</a></li><li><a href="#request_analysis.reanalyze">reanalyze</a></li><li><a href="#request_analysis.setAnalysisRoots">setAnalysisRoots</a></li><li><a href="#request_analysis.setGeneralSubscriptions">setGeneralSubscriptions</a></li><li><a href="#request_analysis.setPriorityFiles">setPriorityFiles</a></li><li><a href="#request_analysis.setSubscriptions">setSubscriptions</a></li><li><a href="#request_analysis.updateContent">updateContent</a></li><li><a href="#request_analysis.updateOptions">updateOptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_analysis.analyzedFiles">analyzedFiles</a></li><li><a href="#notification_analysis.closingLabels">closingLabels</a></li><li><a href="#notification_analysis.errors">errors</a></li><li><a href="#notification_analysis.flushResults">flushResults</a></li><li><a href="#notification_analysis.folding">folding</a></li><li><a href="#notification_analysis.highlights">highlights</a></li><li><a href="#notification_analysis.implemented">implemented</a></li><li><a href="#notification_analysis.invalidate">invalidate</a></li><li><a href="#notification_analysis.navigation">navigation</a></li><li><a href="#notification_analysis.occurrences">occurrences</a></li><li><a href="#notification_analysis.outline">outline</a></li><li><a href="#notification_analysis.overrides">overrides</a></li></ul></div></div><h4>completion (<a href="#domain_completion">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_completion.getSuggestions">getSuggestions</a></li><li><a href="#request_completion.setSubscriptions">setSubscriptions</a></li><li><a href="#request_completion.registerLibraryPaths">registerLibraryPaths</a></li><li><a href="#request_completion.getSuggestionDetails">getSuggestionDetails</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_completion.results">results</a></li><li><a href="#notification_completion.availableSuggestions">availableSuggestions</a></li><li><a href="#notification_completion.existingImports">existingImports</a></li></ul></div></div><h4>search (<a href="#domain_search">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_search.findElementReferences">findElementReferences</a></li><li><a href="#request_search.findMemberDeclarations">findMemberDeclarations</a></li><li><a href="#request_search.findMemberReferences">findMemberReferences</a></li><li><a href="#request_search.findTopLevelDeclarations">findTopLevelDeclarations</a></li><li><a href="#request_search.getTypeHierarchy">getTypeHierarchy</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_search.results">results</a></li></ul></div></div><h4>edit (<a href="#domain_edit">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_edit.format">format</a></li><li><a href="#request_edit.getAssists">getAssists</a></li><li><a href="#request_edit.getAvailableRefactorings">getAvailableRefactorings</a></li><li><a href="#request_edit.getFixes">getFixes</a></li><li><a href="#request_edit.getPostfixCompletion">getPostfixCompletion</a></li><li><a href="#request_edit.getRefactoring">getRefactoring</a></li><li><a href="#request_edit.sortMembers">sortMembers</a></li><li><a href="#request_edit.organizeDirectives">organizeDirectives</a></li></ul></div><h4>execution (<a href="#domain_execution">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_execution.createContext">createContext</a></li><li><a href="#request_execution.deleteContext">deleteContext</a></li><li><a href="#request_execution.getSuggestions">getSuggestions</a></li><li><a href="#request_execution.mapUri">mapUri</a></li><li><a href="#request_execution.setSubscriptions">setSubscriptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_execution.launchData">launchData</a></li></ul></div></div><h4>diagnostic (<a href="#domain_diagnostic">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_diagnostic.getDiagnostics">getDiagnostics</a></li><li><a href="#request_diagnostic.getServerPort">getServerPort</a></li></ul></div><h4>flutter (<a href="#domain_flutter">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_flutter.setSubscriptions">setSubscriptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_flutter.outline">outline</a></li></ul></div></div><h3>Types (<a href="#types">↑</a>)</h3><div class="subindex"><ul><li><a href="#type_AddContentOverlay">AddContentOverlay</a></li><li><a href="#type_AnalysisError">AnalysisError</a></li><li><a href="#type_AnalysisErrorFixes">AnalysisErrorFixes</a></li><li><a href="#type_AnalysisErrorSeverity">AnalysisErrorSeverity</a></li><li><a href="#type_AnalysisErrorType">AnalysisErrorType</a></li><li><a href="#type_AnalysisOptions">AnalysisOptions</a></li><li><a href="#type_AnalysisService">AnalysisService</a></li><li><a href="#type_AnalysisStatus">AnalysisStatus</a></li><li><a href="#type_AvailableSuggestion">AvailableSuggestion</a></li><li><a href="#type_AvailableSuggestionRelevanceTag">AvailableSuggestionRelevanceTag</a></li><li><a href="#type_AvailableSuggestionSet">AvailableSuggestionSet</a></li><li><a href="#type_ChangeContentOverlay">ChangeContentOverlay</a></li><li><a href="#type_ClosingLabel">ClosingLabel</a></li><li><a href="#type_CompletionId">CompletionId</a></li><li><a href="#type_CompletionService">CompletionService</a></li><li><a href="#type_CompletionSuggestion">CompletionSuggestion</a></li><li><a href="#type_CompletionSuggestionKind">CompletionSuggestionKind</a></li><li><a href="#type_ContextData">ContextData</a></li><li><a href="#type_DiagnosticMessage">DiagnosticMessage</a></li><li><a href="#type_Element">Element</a></li><li><a href="#type_ElementDeclaration">ElementDeclaration</a></li><li><a href="#type_ElementKind">ElementKind</a></li><li><a href="#type_ExecutableFile">ExecutableFile</a></li><li><a href="#type_ExecutableKind">ExecutableKind</a></li><li><a href="#type_ExecutionContextId">ExecutionContextId</a></li><li><a href="#type_ExecutionService">ExecutionService</a></li><li><a href="#type_ExistingImport">ExistingImport</a></li><li><a href="#type_ExistingImports">ExistingImports</a></li><li><a href="#type_FileKind">FileKind</a></li><li><a href="#type_FilePath">FilePath</a></li><li><a href="#type_FlutterOutline">FlutterOutline</a></li><li><a href="#type_FlutterOutlineAttribute">FlutterOutlineAttribute</a></li><li><a href="#type_FlutterOutlineKind">FlutterOutlineKind</a></li><li><a href="#type_FlutterService">FlutterService</a></li><li><a href="#type_FlutterWidgetProperty">FlutterWidgetProperty</a></li><li><a href="#type_FlutterWidgetPropertyEditor">FlutterWidgetPropertyEditor</a></li><li><a href="#type_FlutterWidgetPropertyEditorKind">FlutterWidgetPropertyEditorKind</a></li><li><a href="#type_FlutterWidgetPropertyValue">FlutterWidgetPropertyValue</a></li><li><a href="#type_FlutterWidgetPropertyValueEnumItem">FlutterWidgetPropertyValueEnumItem</a></li><li><a href="#type_FoldingKind">FoldingKind</a></li><li><a href="#type_FoldingRegion">FoldingRegion</a></li><li><a href="#type_GeneralAnalysisService">GeneralAnalysisService</a></li><li><a href="#type_HighlightRegion">HighlightRegion</a></li><li><a href="#type_HighlightRegionType">HighlightRegionType</a></li><li><a href="#type_HoverInformation">HoverInformation</a></li><li><a href="#type_ImplementedClass">ImplementedClass</a></li><li><a href="#type_ImplementedMember">ImplementedMember</a></li><li><a href="#type_ImportedElementSet">ImportedElementSet</a></li><li><a href="#type_ImportedElements">ImportedElements</a></li><li><a href="#type_IncludedSuggestionRelevanceTag">IncludedSuggestionRelevanceTag</a></li><li><a href="#type_IncludedSuggestionSet">IncludedSuggestionSet</a></li><li><a href="#type_KytheEntry">KytheEntry</a></li><li><a href="#type_KytheVName">KytheVName</a></li><li><a href="#type_LibraryPathSet">LibraryPathSet</a></li><li><a href="#type_LinkedEditGroup">LinkedEditGroup</a></li><li><a href="#type_LinkedEditSuggestion">LinkedEditSuggestion</a></li><li><a href="#type_LinkedEditSuggestionKind">LinkedEditSuggestionKind</a></li><li><a href="#type_Location">Location</a></li><li><a href="#type_NavigationRegion">NavigationRegion</a></li><li><a href="#type_NavigationTarget">NavigationTarget</a></li><li><a href="#type_Occurrences">Occurrences</a></li><li><a href="#type_Outline">Outline</a></li><li><a href="#type_OverriddenMember">OverriddenMember</a></li><li><a href="#type_Override">Override</a></li><li><a href="#type_Position">Position</a></li><li><a href="#type_PostfixTemplateDescriptor">PostfixTemplateDescriptor</a></li><li><a href="#type_PubStatus">PubStatus</a></li><li><a href="#type_RefactoringFeedback">RefactoringFeedback</a></li><li><a href="#type_RefactoringKind">RefactoringKind</a></li><li><a href="#type_RefactoringMethodParameter">RefactoringMethodParameter</a></li><li><a href="#type_RefactoringMethodParameterKind">RefactoringMethodParameterKind</a></li><li><a href="#type_RefactoringOptions">RefactoringOptions</a></li><li><a href="#type_RefactoringProblem">RefactoringProblem</a></li><li><a href="#type_RefactoringProblemSeverity">RefactoringProblemSeverity</a></li><li><a href="#type_RemoveContentOverlay">RemoveContentOverlay</a></li><li><a href="#type_RequestError">RequestError</a></li><li><a href="#type_RequestErrorCode">RequestErrorCode</a></li><li><a href="#type_RuntimeCompletionExpression">RuntimeCompletionExpression</a></li><li><a href="#type_RuntimeCompletionExpressionType">RuntimeCompletionExpressionType</a></li><li><a href="#type_RuntimeCompletionExpressionTypeKind">RuntimeCompletionExpressionTypeKind</a></li><li><a href="#type_RuntimeCompletionVariable">RuntimeCompletionVariable</a></li><li><a href="#type_SearchId">SearchId</a></li><li><a href="#type_SearchResult">SearchResult</a></li><li><a href="#type_SearchResultKind">SearchResultKind</a></li><li><a href="#type_ServerService">ServerService</a></li><li><a href="#type_SourceChange">SourceChange</a></li><li><a href="#type_SourceEdit">SourceEdit</a></li><li><a href="#type_SourceFileEdit">SourceFileEdit</a></li><li><a href="#type_TypeHierarchyItem">TypeHierarchyItem</a></li></ul></div><h3>Refactorings (<a href="#refactorings">↑</a>)</h3><div class="subindex"><ul><li><a href="#refactoring_CONVERT_GETTER_TO_METHOD">CONVERT_GETTER_TO_METHOD</a></li><li><a href="#refactoring_CONVERT_METHOD_TO_GETTER">CONVERT_METHOD_TO_GETTER</a></li><li><a href="#refactoring_EXTRACT_LOCAL_VARIABLE">EXTRACT_LOCAL_VARIABLE</a></li><li><a href="#refactoring_EXTRACT_METHOD">EXTRACT_METHOD</a></li><li><a href="#refactoring_EXTRACT_WIDGET">EXTRACT_WIDGET</a></li><li><a href="#refactoring_INLINE_LOCAL_VARIABLE">INLINE_LOCAL_VARIABLE</a></li><li><a href="#refactoring_INLINE_METHOD">INLINE_METHOD</a></li><li><a href="#refactoring_MOVE_FILE">MOVE_FILE</a></li><li><a href="#refactoring_RENAME">RENAME</a></li></ul></div>
+<h3>Domains</h3><h4>server (<a href="#domain_server">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_server.getVersion">getVersion</a></li><li><a href="#request_server.shutdown">shutdown</a></li><li><a href="#request_server.setSubscriptions">setSubscriptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_server.connected">connected</a></li><li><a href="#notification_server.error">error</a></li><li><a href="#notification_server.log">log</a></li><li><a href="#notification_server.status">status</a></li></ul></div></div><h4>analysis (<a href="#domain_analysis">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_analysis.getErrors">getErrors</a></li><li><a href="#request_analysis.getHover">getHover</a></li><li><a href="#request_analysis.getLibraryDependencies">getLibraryDependencies</a></li><li><a href="#request_analysis.getNavigation">getNavigation</a></li><li><a href="#request_analysis.getReachableSources">getReachableSources</a></li><li><a href="#request_analysis.reanalyze">reanalyze</a></li><li><a href="#request_analysis.setAnalysisRoots">setAnalysisRoots</a></li><li><a href="#request_analysis.setGeneralSubscriptions">setGeneralSubscriptions</a></li><li><a href="#request_analysis.setPriorityFiles">setPriorityFiles</a></li><li><a href="#request_analysis.setSubscriptions">setSubscriptions</a></li><li><a href="#request_analysis.updateContent">updateContent</a></li><li><a href="#request_analysis.updateOptions">updateOptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_analysis.analyzedFiles">analyzedFiles</a></li><li><a href="#notification_analysis.closingLabels">closingLabels</a></li><li><a href="#notification_analysis.errors">errors</a></li><li><a href="#notification_analysis.flushResults">flushResults</a></li><li><a href="#notification_analysis.folding">folding</a></li><li><a href="#notification_analysis.highlights">highlights</a></li><li><a href="#notification_analysis.implemented">implemented</a></li><li><a href="#notification_analysis.invalidate">invalidate</a></li><li><a href="#notification_analysis.navigation">navigation</a></li><li><a href="#notification_analysis.occurrences">occurrences</a></li><li><a href="#notification_analysis.outline">outline</a></li><li><a href="#notification_analysis.overrides">overrides</a></li></ul></div></div><h4>completion (<a href="#domain_completion">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_completion.getSuggestions">getSuggestions</a></li><li><a href="#request_completion.setSubscriptions">setSubscriptions</a></li><li><a href="#request_completion.registerLibraryPaths">registerLibraryPaths</a></li><li><a href="#request_completion.getSuggestionDetails">getSuggestionDetails</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_completion.results">results</a></li><li><a href="#notification_completion.availableSuggestions">availableSuggestions</a></li><li><a href="#notification_completion.existingImports">existingImports</a></li></ul></div></div><h4>search (<a href="#domain_search">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_search.findElementReferences">findElementReferences</a></li><li><a href="#request_search.findMemberDeclarations">findMemberDeclarations</a></li><li><a href="#request_search.findMemberReferences">findMemberReferences</a></li><li><a href="#request_search.findTopLevelDeclarations">findTopLevelDeclarations</a></li><li><a href="#request_search.getTypeHierarchy">getTypeHierarchy</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_search.results">results</a></li></ul></div></div><h4>edit (<a href="#domain_edit">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_edit.format">format</a></li><li><a href="#request_edit.getAssists">getAssists</a></li><li><a href="#request_edit.getAvailableRefactorings">getAvailableRefactorings</a></li><li><a href="#request_edit.getFixes">getFixes</a></li><li><a href="#request_edit.getPostfixCompletion">getPostfixCompletion</a></li><li><a href="#request_edit.getRefactoring">getRefactoring</a></li><li><a href="#request_edit.sortMembers">sortMembers</a></li><li><a href="#request_edit.organizeDirectives">organizeDirectives</a></li></ul></div><h4>execution (<a href="#domain_execution">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_execution.createContext">createContext</a></li><li><a href="#request_execution.deleteContext">deleteContext</a></li><li><a href="#request_execution.getSuggestions">getSuggestions</a></li><li><a href="#request_execution.mapUri">mapUri</a></li><li><a href="#request_execution.setSubscriptions">setSubscriptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_execution.launchData">launchData</a></li></ul></div></div><h4>diagnostic (<a href="#domain_diagnostic">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_diagnostic.getDiagnostics">getDiagnostics</a></li><li><a href="#request_diagnostic.getServerPort">getServerPort</a></li></ul></div><h4>flutter (<a href="#domain_flutter">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_flutter.setSubscriptions">setSubscriptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_flutter.outline">outline</a></li></ul></div></div><h3>Types (<a href="#types">↑</a>)</h3><div class="subindex"><ul><li><a href="#type_AddContentOverlay">AddContentOverlay</a></li><li><a href="#type_AnalysisError">AnalysisError</a></li><li><a href="#type_AnalysisErrorFixes">AnalysisErrorFixes</a></li><li><a href="#type_AnalysisErrorSeverity">AnalysisErrorSeverity</a></li><li><a href="#type_AnalysisErrorType">AnalysisErrorType</a></li><li><a href="#type_AnalysisOptions">AnalysisOptions</a></li><li><a href="#type_AnalysisService">AnalysisService</a></li><li><a href="#type_AnalysisStatus">AnalysisStatus</a></li><li><a href="#type_AvailableSuggestion">AvailableSuggestion</a></li><li><a href="#type_AvailableSuggestionRelevanceTag">AvailableSuggestionRelevanceTag</a></li><li><a href="#type_AvailableSuggestionSet">AvailableSuggestionSet</a></li><li><a href="#type_BulkFix">BulkFix</a></li><li><a href="#type_BulkFixDetail">BulkFixDetail</a></li><li><a href="#type_ChangeContentOverlay">ChangeContentOverlay</a></li><li><a href="#type_ClosingLabel">ClosingLabel</a></li><li><a href="#type_CompletionId">CompletionId</a></li><li><a href="#type_CompletionService">CompletionService</a></li><li><a href="#type_CompletionSuggestion">CompletionSuggestion</a></li><li><a href="#type_CompletionSuggestionKind">CompletionSuggestionKind</a></li><li><a href="#type_ContextData">ContextData</a></li><li><a href="#type_DiagnosticMessage">DiagnosticMessage</a></li><li><a href="#type_Element">Element</a></li><li><a href="#type_ElementDeclaration">ElementDeclaration</a></li><li><a href="#type_ElementKind">ElementKind</a></li><li><a href="#type_ExecutableFile">ExecutableFile</a></li><li><a href="#type_ExecutableKind">ExecutableKind</a></li><li><a href="#type_ExecutionContextId">ExecutionContextId</a></li><li><a href="#type_ExecutionService">ExecutionService</a></li><li><a href="#type_ExistingImport">ExistingImport</a></li><li><a href="#type_ExistingImports">ExistingImports</a></li><li><a href="#type_FileKind">FileKind</a></li><li><a href="#type_FilePath">FilePath</a></li><li><a href="#type_FlutterOutline">FlutterOutline</a></li><li><a href="#type_FlutterOutlineAttribute">FlutterOutlineAttribute</a></li><li><a href="#type_FlutterOutlineKind">FlutterOutlineKind</a></li><li><a href="#type_FlutterService">FlutterService</a></li><li><a href="#type_FlutterWidgetProperty">FlutterWidgetProperty</a></li><li><a href="#type_FlutterWidgetPropertyEditor">FlutterWidgetPropertyEditor</a></li><li><a href="#type_FlutterWidgetPropertyEditorKind">FlutterWidgetPropertyEditorKind</a></li><li><a href="#type_FlutterWidgetPropertyValue">FlutterWidgetPropertyValue</a></li><li><a href="#type_FlutterWidgetPropertyValueEnumItem">FlutterWidgetPropertyValueEnumItem</a></li><li><a href="#type_FoldingKind">FoldingKind</a></li><li><a href="#type_FoldingRegion">FoldingRegion</a></li><li><a href="#type_GeneralAnalysisService">GeneralAnalysisService</a></li><li><a href="#type_HighlightRegion">HighlightRegion</a></li><li><a href="#type_HighlightRegionType">HighlightRegionType</a></li><li><a href="#type_HoverInformation">HoverInformation</a></li><li><a href="#type_ImplementedClass">ImplementedClass</a></li><li><a href="#type_ImplementedMember">ImplementedMember</a></li><li><a href="#type_ImportedElementSet">ImportedElementSet</a></li><li><a href="#type_ImportedElements">ImportedElements</a></li><li><a href="#type_IncludedSuggestionRelevanceTag">IncludedSuggestionRelevanceTag</a></li><li><a href="#type_IncludedSuggestionSet">IncludedSuggestionSet</a></li><li><a href="#type_KytheEntry">KytheEntry</a></li><li><a href="#type_KytheVName">KytheVName</a></li><li><a href="#type_LibraryPathSet">LibraryPathSet</a></li><li><a href="#type_LinkedEditGroup">LinkedEditGroup</a></li><li><a href="#type_LinkedEditSuggestion">LinkedEditSuggestion</a></li><li><a href="#type_LinkedEditSuggestionKind">LinkedEditSuggestionKind</a></li><li><a href="#type_Location">Location</a></li><li><a href="#type_NavigationRegion">NavigationRegion</a></li><li><a href="#type_NavigationTarget">NavigationTarget</a></li><li><a href="#type_Occurrences">Occurrences</a></li><li><a href="#type_Outline">Outline</a></li><li><a href="#type_OverriddenMember">OverriddenMember</a></li><li><a href="#type_Override">Override</a></li><li><a href="#type_Position">Position</a></li><li><a href="#type_PostfixTemplateDescriptor">PostfixTemplateDescriptor</a></li><li><a href="#type_PubStatus">PubStatus</a></li><li><a href="#type_RefactoringFeedback">RefactoringFeedback</a></li><li><a href="#type_RefactoringKind">RefactoringKind</a></li><li><a href="#type_RefactoringMethodParameter">RefactoringMethodParameter</a></li><li><a href="#type_RefactoringMethodParameterKind">RefactoringMethodParameterKind</a></li><li><a href="#type_RefactoringOptions">RefactoringOptions</a></li><li><a href="#type_RefactoringProblem">RefactoringProblem</a></li><li><a href="#type_RefactoringProblemSeverity">RefactoringProblemSeverity</a></li><li><a href="#type_RemoveContentOverlay">RemoveContentOverlay</a></li><li><a href="#type_RequestError">RequestError</a></li><li><a href="#type_RequestErrorCode">RequestErrorCode</a></li><li><a href="#type_RuntimeCompletionExpression">RuntimeCompletionExpression</a></li><li><a href="#type_RuntimeCompletionExpressionType">RuntimeCompletionExpressionType</a></li><li><a href="#type_RuntimeCompletionExpressionTypeKind">RuntimeCompletionExpressionTypeKind</a></li><li><a href="#type_RuntimeCompletionVariable">RuntimeCompletionVariable</a></li><li><a href="#type_SearchId">SearchId</a></li><li><a href="#type_SearchResult">SearchResult</a></li><li><a href="#type_SearchResultKind">SearchResultKind</a></li><li><a href="#type_ServerService">ServerService</a></li><li><a href="#type_SourceChange">SourceChange</a></li><li><a href="#type_SourceEdit">SourceEdit</a></li><li><a href="#type_SourceFileEdit">SourceFileEdit</a></li><li><a href="#type_TypeHierarchyItem">TypeHierarchyItem</a></li></ul></div><h3>Refactorings (<a href="#refactorings">↑</a>)</h3><div class="subindex"><ul><li><a href="#refactoring_CONVERT_GETTER_TO_METHOD">CONVERT_GETTER_TO_METHOD</a></li><li><a href="#refactoring_CONVERT_METHOD_TO_GETTER">CONVERT_METHOD_TO_GETTER</a></li><li><a href="#refactoring_EXTRACT_LOCAL_VARIABLE">EXTRACT_LOCAL_VARIABLE</a></li><li><a href="#refactoring_EXTRACT_METHOD">EXTRACT_METHOD</a></li><li><a href="#refactoring_EXTRACT_WIDGET">EXTRACT_WIDGET</a></li><li><a href="#refactoring_INLINE_LOCAL_VARIABLE">INLINE_LOCAL_VARIABLE</a></li><li><a href="#refactoring_INLINE_METHOD">INLINE_METHOD</a></li><li><a href="#refactoring_MOVE_FILE">MOVE_FILE</a></li><li><a href="#refactoring_RENAME">RENAME</a></li></ul></div>
 
 
 </body></html>
\ No newline at end of file
diff --git a/pkg/analysis_server/lib/protocol/protocol_constants.dart b/pkg/analysis_server/lib/protocol/protocol_constants.dart
index 61ada08..cdccff0 100644
--- a/pkg/analysis_server/lib/protocol/protocol_constants.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_constants.dart
@@ -6,7 +6,7 @@
 // To regenerate the file, use the script
 // "pkg/analysis_server/tool/spec/generate_files".
 
-const String PROTOCOL_VERSION = '1.31.0';
+const String PROTOCOL_VERSION = '1.32.0';
 
 const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
 const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';
@@ -222,6 +222,7 @@
 const String EDIT_REQUEST_ORGANIZE_DIRECTIVES_FILE = 'file';
 const String EDIT_REQUEST_SORT_MEMBERS = 'edit.sortMembers';
 const String EDIT_REQUEST_SORT_MEMBERS_FILE = 'file';
+const String EDIT_RESPONSE_BULK_FIXES_DETAILS = 'details';
 const String EDIT_RESPONSE_BULK_FIXES_EDITS = 'edits';
 const String EDIT_RESPONSE_DARTFIX_DETAILS = 'details';
 const String EDIT_RESPONSE_DARTFIX_EDITS = 'edits';
diff --git a/pkg/analysis_server/lib/protocol/protocol_generated.dart b/pkg/analysis_server/lib/protocol/protocol_generated.dart
index 06c3551..3cf16bf 100644
--- a/pkg/analysis_server/lib/protocol/protocol_generated.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_generated.dart
@@ -4955,6 +4955,188 @@
   }
 }
 
+/// BulkFix
+///
+/// {
+///   "path": FilePath
+///   "fixes": List<BulkFixDetail>
+/// }
+///
+/// Clients may not extend, implement or mix-in this class.
+class BulkFix implements HasToJson {
+  String _path;
+
+  List<BulkFixDetail> _fixes;
+
+  /// The path of the library.
+  String get path => _path;
+
+  /// The path of the library.
+  set path(String value) {
+    assert(value != null);
+    _path = value;
+  }
+
+  /// A list of bulk fix details.
+  List<BulkFixDetail> get fixes => _fixes;
+
+  /// A list of bulk fix details.
+  set fixes(List<BulkFixDetail> value) {
+    assert(value != null);
+    _fixes = value;
+  }
+
+  BulkFix(String path, List<BulkFixDetail> fixes) {
+    this.path = path;
+    this.fixes = fixes;
+  }
+
+  factory BulkFix.fromJson(
+      JsonDecoder jsonDecoder, String jsonPath, Object json) {
+    json ??= {};
+    if (json is Map) {
+      String path;
+      if (json.containsKey('path')) {
+        path = jsonDecoder.decodeString(jsonPath + '.path', json['path']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'path');
+      }
+      List<BulkFixDetail> fixes;
+      if (json.containsKey('fixes')) {
+        fixes = jsonDecoder.decodeList(
+            jsonPath + '.fixes',
+            json['fixes'],
+            (String jsonPath, Object json) =>
+                BulkFixDetail.fromJson(jsonDecoder, jsonPath, json));
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'fixes');
+      }
+      return BulkFix(path, fixes);
+    } else {
+      throw jsonDecoder.mismatch(jsonPath, 'BulkFix', json);
+    }
+  }
+
+  @override
+  Map<String, dynamic> toJson() {
+    var result = <String, dynamic>{};
+    result['path'] = path;
+    result['fixes'] =
+        fixes.map((BulkFixDetail value) => value.toJson()).toList();
+    return result;
+  }
+
+  @override
+  String toString() => json.encode(toJson());
+
+  @override
+  bool operator ==(other) {
+    if (other is BulkFix) {
+      return path == other.path &&
+          listEqual(
+              fixes, other.fixes, (BulkFixDetail a, BulkFixDetail b) => a == b);
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode {
+    var hash = 0;
+    hash = JenkinsSmiHash.combine(hash, path.hashCode);
+    hash = JenkinsSmiHash.combine(hash, fixes.hashCode);
+    return JenkinsSmiHash.finish(hash);
+  }
+}
+
+/// BulkFixDetail
+///
+/// {
+///   "code": String
+///   "occurrences": int
+/// }
+///
+/// Clients may not extend, implement or mix-in this class.
+class BulkFixDetail implements HasToJson {
+  String _code;
+
+  int _occurrences;
+
+  /// The code of the diagnostic associated with the fix.
+  String get code => _code;
+
+  /// The code of the diagnostic associated with the fix.
+  set code(String value) {
+    assert(value != null);
+    _code = value;
+  }
+
+  /// The number times the associated diagnostic was fixed in the associated
+  /// source edit.
+  int get occurrences => _occurrences;
+
+  /// The number times the associated diagnostic was fixed in the associated
+  /// source edit.
+  set occurrences(int value) {
+    assert(value != null);
+    _occurrences = value;
+  }
+
+  BulkFixDetail(String code, int occurrences) {
+    this.code = code;
+    this.occurrences = occurrences;
+  }
+
+  factory BulkFixDetail.fromJson(
+      JsonDecoder jsonDecoder, String jsonPath, Object json) {
+    json ??= {};
+    if (json is Map) {
+      String code;
+      if (json.containsKey('code')) {
+        code = jsonDecoder.decodeString(jsonPath + '.code', json['code']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'code');
+      }
+      int occurrences;
+      if (json.containsKey('occurrences')) {
+        occurrences = jsonDecoder.decodeInt(
+            jsonPath + '.occurrences', json['occurrences']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'occurrences');
+      }
+      return BulkFixDetail(code, occurrences);
+    } else {
+      throw jsonDecoder.mismatch(jsonPath, 'BulkFixDetail', json);
+    }
+  }
+
+  @override
+  Map<String, dynamic> toJson() {
+    var result = <String, dynamic>{};
+    result['code'] = code;
+    result['occurrences'] = occurrences;
+    return result;
+  }
+
+  @override
+  String toString() => json.encode(toJson());
+
+  @override
+  bool operator ==(other) {
+    if (other is BulkFixDetail) {
+      return code == other.code && occurrences == other.occurrences;
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode {
+    var hash = 0;
+    hash = JenkinsSmiHash.combine(hash, code.hashCode);
+    hash = JenkinsSmiHash.combine(hash, occurrences.hashCode);
+    return JenkinsSmiHash.finish(hash);
+  }
+}
+
 /// ClosingLabel
 ///
 /// {
@@ -7210,12 +7392,15 @@
 ///
 /// {
 ///   "edits": List<SourceFileEdit>
+///   "details": List<BulkFix>
 /// }
 ///
 /// Clients may not extend, implement or mix-in this class.
 class EditBulkFixesResult implements ResponseResult {
   List<SourceFileEdit> _edits;
 
+  List<BulkFix> _details;
+
   /// A list of source edits to apply the recommended changes.
   List<SourceFileEdit> get edits => _edits;
 
@@ -7225,8 +7410,18 @@
     _edits = value;
   }
 
-  EditBulkFixesResult(List<SourceFileEdit> edits) {
+  /// Details that summarize the fixes associated with the recommended changes.
+  List<BulkFix> get details => _details;
+
+  /// Details that summarize the fixes associated with the recommended changes.
+  set details(List<BulkFix> value) {
+    assert(value != null);
+    _details = value;
+  }
+
+  EditBulkFixesResult(List<SourceFileEdit> edits, List<BulkFix> details) {
     this.edits = edits;
+    this.details = details;
   }
 
   factory EditBulkFixesResult.fromJson(
@@ -7243,7 +7438,17 @@
       } else {
         throw jsonDecoder.mismatch(jsonPath, 'edits');
       }
-      return EditBulkFixesResult(edits);
+      List<BulkFix> details;
+      if (json.containsKey('details')) {
+        details = jsonDecoder.decodeList(
+            jsonPath + '.details',
+            json['details'],
+            (String jsonPath, Object json) =>
+                BulkFix.fromJson(jsonDecoder, jsonPath, json));
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'details');
+      }
+      return EditBulkFixesResult(edits, details);
     } else {
       throw jsonDecoder.mismatch(jsonPath, 'edit.bulkFixes result', json);
     }
@@ -7261,6 +7466,7 @@
     var result = <String, dynamic>{};
     result['edits'] =
         edits.map((SourceFileEdit value) => value.toJson()).toList();
+    result['details'] = details.map((BulkFix value) => value.toJson()).toList();
     return result;
   }
 
@@ -7275,8 +7481,9 @@
   @override
   bool operator ==(other) {
     if (other is EditBulkFixesResult) {
-      return listEqual(
-          edits, other.edits, (SourceFileEdit a, SourceFileEdit b) => a == b);
+      return listEqual(edits, other.edits,
+              (SourceFileEdit a, SourceFileEdit b) => a == b) &&
+          listEqual(details, other.details, (BulkFix a, BulkFix b) => a == b);
     }
     return false;
   }
@@ -7285,6 +7492,7 @@
   int get hashCode {
     var hash = 0;
     hash = JenkinsSmiHash.combine(hash, edits.hashCode);
+    hash = JenkinsSmiHash.combine(hash, details.hashCode);
     return JenkinsSmiHash.finish(hash);
   }
 }
diff --git a/pkg/analysis_server/lib/src/edit/edit_domain.dart b/pkg/analysis_server/lib/src/edit/edit_domain.dart
index 62a8d6a..3cd4538 100644
--- a/pkg/analysis_server/lib/src/edit/edit_domain.dart
+++ b/pkg/analysis_server/lib/src/edit/edit_domain.dart
@@ -121,7 +121,8 @@
       );
       var changeBuilder = await processor.fixErrors(collection.contexts);
 
-      var response = EditBulkFixesResult(changeBuilder.sourceChange.edits)
+      var response = EditBulkFixesResult(
+              changeBuilder.sourceChange.edits, processor.fixDetails)
           .toResponse(request.id);
       server.sendResponse(response);
     } catch (exception, stackTrace) {
diff --git a/pkg/analysis_server/lib/src/server/driver.dart b/pkg/analysis_server/lib/src/server/driver.dart
index db988fa..9ad340f 100644
--- a/pkg/analysis_server/lib/src/server/driver.dart
+++ b/pkg/analysis_server/lib/src/server/driver.dart
@@ -74,22 +74,26 @@
 
   /// The name of the option used to cause instrumentation to also be written to
   /// a local file.
-  static const String INSTRUMENTATION_LOG_FILE = 'instrumentation-log-file';
+  static const String PROTOCOL_TRAFFIC_LOG = 'protocol-traffic-log';
+  static const String PROTOCOL_TRAFFIC_LOG_ALIAS = 'instrumentation-log-file';
 
   /// The name of the option used to specify if [print] should print to the
   /// console instead of being intercepted.
   static const String INTERNAL_PRINT_TO_CONSOLE = 'internal-print-to-console';
 
   /// The name of the option used to describe the new analysis driver logger.
-  static const String NEW_ANALYSIS_DRIVER_LOG = 'new-analysis-driver-log';
+  static const String ANALYSIS_DRIVER_LOG = 'analysis-driver-log';
+  static const String ANALYSIS_DRIVER_LOG_ALIAS = 'new-analysis-driver-log';
 
   /// The option for specifying the http diagnostic port.
   /// If specified, users can review server status and performance information
   /// by opening a web browser on http://localhost:<port>
-  static const String PORT_OPTION = 'port';
+  static const String DIAGNOSTIC_PORT = 'diagnostic-port';
+  static const String DIAGNOSTIC_PORT_ALIAS = 'port';
 
   /// The path to the SDK.
-  static const String SDK_OPTION = 'sdk';
+  static const String DART_SDK = 'dart-sdk';
+  static const String DART_SDK_ALIAS = 'sdk';
 
   /// The path to the data cache.
   static const String CACHE_FOLDER = 'cache';
@@ -126,7 +130,7 @@
 
     var analysisServerOptions = AnalysisServerOptions();
     analysisServerOptions.newAnalysisDriverLog =
-        results[NEW_ANALYSIS_DRIVER_LOG];
+        results[ANALYSIS_DRIVER_LOG] ?? results[ANALYSIS_DRIVER_LOG_ALIAS];
     analysisServerOptions.clientId = results[CLIENT_ID];
     analysisServerOptions.useLanguageServerProtocol = results[USE_LSP];
     // For clients that don't supply their own identifier, use a default based on
@@ -220,7 +224,8 @@
     //
     // Initialize the instrumentation service.
     //
-    String logFilePath = results[INSTRUMENTATION_LOG_FILE];
+    String logFilePath =
+        results[PROTOCOL_TRAFFIC_LOG] ?? results[PROTOCOL_TRAFFIC_LOG_ALIAS];
     var allInstrumentationServices = instrumentationService == null
         ? <InstrumentationService>[]
         : [instrumentationService];
@@ -248,11 +253,13 @@
     AnalysisEngine.instance.instrumentationService = instrumentationService;
 
     int diagnosticServerPort;
-    if (results[PORT_OPTION] != null) {
+    final String portValue =
+        results[DIAGNOSTIC_PORT] ?? results[DIAGNOSTIC_PORT_ALIAS];
+    if (portValue != null) {
       try {
-        diagnosticServerPort = int.parse(results[PORT_OPTION]);
+        diagnosticServerPort = int.parse(portValue);
       } on FormatException {
-        print('Invalid port number: ${results[PORT_OPTION]}');
+        print('Invalid port number: $portValue');
         print('');
         _printUsage(parser, analytics);
         exitCode = 1;
@@ -480,32 +487,59 @@
     parser.addFlag(HELP_OPTION,
         abbr: 'h', negatable: false, help: 'Print this usage information.');
     parser.addOption(CLIENT_ID,
-        valueHelp: 'name', help: 'An identifier used to identify the client.');
+        valueHelp: 'name',
+        help: 'An identifier for the analysis server client.');
     parser.addOption(CLIENT_VERSION,
-        valueHelp: 'version', help: 'The version of the client.');
-
+        valueHelp: 'version',
+        help: 'The version of the analysis server client.');
+    parser.addOption(DART_SDK,
+        valueHelp: 'path', help: 'Override the Dart SDK to use for analysis.');
+    parser.addOption(DART_SDK_ALIAS, hide: true);
+    parser.addOption(CACHE_FOLDER,
+        valueHelp: 'path',
+        help: 'Override the location of the analysis server\'s cache.');
     parser.addFlag(USE_LSP,
         defaultsTo: false,
         negatable: false,
         help: 'Whether to use the Language Server Protocol (LSP).');
 
-    parser.addOption(SDK_OPTION,
-        valueHelp: 'path', help: 'The path to the Dart SDK.');
-    parser.addOption(CACHE_FOLDER,
-        valueHelp: 'path',
-        help: 'The path to the location to write cache data.');
+    parser.addSeparator('Server diagnostics:');
 
-    parser.addOption(INSTRUMENTATION_LOG_FILE,
+    parser.addOption(PROTOCOL_TRAFFIC_LOG,
         valueHelp: 'file path',
-        help: 'Write instrumentation data to the given file.');
-    parser.addOption(NEW_ANALYSIS_DRIVER_LOG,
-        valueHelp: 'path',
-        help: "Set a destination for the new analysis driver's log.");
-    parser.addOption(PORT_OPTION,
-        valueHelp: 'port',
-        help: 'The http diagnostic port to serve status and performance '
-            'information.');
+        help: 'Write server protocol traffic to the given file.');
+    parser.addOption(PROTOCOL_TRAFFIC_LOG_ALIAS, hide: true);
 
+    parser.addOption(ANALYSIS_DRIVER_LOG,
+        valueHelp: 'file path',
+        help: 'Write analysis driver diagnostic data to the given file.');
+    parser.addOption(ANALYSIS_DRIVER_LOG_ALIAS, hide: true);
+
+    parser.addOption(DIAGNOSTIC_PORT,
+        valueHelp: 'port',
+        help: 'Serve a web UI for status and performance data on the given '
+            'port.');
+    parser.addOption(DIAGNOSTIC_PORT_ALIAS, hide: true);
+
+    //
+    // Hidden; these have not yet been made public.
+    //
+    parser.addFlag(ANALYTICS_FLAG,
+        help: 'enable or disable sending analytics information to Google',
+        hide: !telemetry.SHOW_ANALYTICS_UI);
+    parser.addFlag(SUPPRESS_ANALYTICS_FLAG,
+        negatable: false,
+        help: 'suppress analytics for this session',
+        hide: !telemetry.SHOW_ANALYTICS_UI);
+
+    //
+    // Hidden; these are for internal development.
+    //
+    parser.addOption(TRAIN_USING,
+        valueHelp: 'path',
+        help: 'Pass in a directory to analyze for purposes of training an '
+            'analysis server snapshot.',
+        hide: true);
     parser.addFlag(DISABLE_SERVER_EXCEPTION_HANDLING,
         // TODO(jcollins-g): Pipeline option through and apply to all
         // exception-nullifying runZoned() calls.
@@ -523,21 +557,8 @@
         negatable: false,
         hide: true);
 
-    parser.addFlag(ANALYTICS_FLAG,
-        help: 'enable or disable sending analytics information to Google',
-        hide: !telemetry.SHOW_ANALYTICS_UI);
-    parser.addFlag(SUPPRESS_ANALYTICS_FLAG,
-        negatable: false,
-        help: 'suppress analytics for this session',
-        hide: !telemetry.SHOW_ANALYTICS_UI);
-
-    parser.addOption(TRAIN_USING,
-        valueHelp: 'path',
-        help: 'Pass in a directory to analyze for purposes of training an '
-            'analysis server snapshot.');
-
     //
-    // Deprecated options - no longer read from.
+    // Hidden; these are deprecated and no longer read from.
     //
 
     // Removed 11/15/2020.
@@ -582,8 +603,10 @@
   }
 
   String _getSdkPath(ArgResults args) {
-    if (args[SDK_OPTION] != null) {
-      return args[SDK_OPTION];
+    if (args[DART_SDK] != null) {
+      return args[DART_SDK];
+    } else if (args[DART_SDK_ALIAS] != null) {
+      return args[DART_SDK_ALIAS];
     } else {
       return getSdkPath();
     }
diff --git a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
index 2e797f0..a8f267c 100644
--- a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
+++ b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
@@ -5,6 +5,7 @@
 import 'dart:core';
 
 import 'package:analysis_server/plugin/edit/fix/fix_dart.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
 import 'package:analysis_server/src/services/correction/change_workspace.dart';
 import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
 import 'package:analysis_server/src/services/correction/dart/add_await.dart';
@@ -360,6 +361,18 @@
   BulkFixProcessor(this.instrumentationService, this.workspace)
       : builder = ChangeBuilder(workspace: workspace);
 
+  List<BulkFix> get fixDetails {
+    var details = <BulkFix>[];
+    for (var change in changeMap.libraryMap.entries) {
+      var fixes = <BulkFixDetail>[];
+      for (var codeEntry in change.value.entries) {
+        fixes.add(BulkFixDetail(codeEntry.key, codeEntry.value));
+      }
+      details.add(BulkFix(change.key, fixes));
+    }
+    return details;
+  }
+
   /// Return a change builder that has been used to create fixes for the
   /// diagnostics in the libraries in the given [contexts].
   Future<ChangeBuilder> fixErrors(List<AnalysisContext> contexts) async {
diff --git a/pkg/analysis_server/test/analysis_abstract.dart b/pkg/analysis_server/test/analysis_abstract.dart
index 8a90649..dd5d637 100644
--- a/pkg/analysis_server/test/analysis_abstract.dart
+++ b/pkg/analysis_server/test/analysis_abstract.dart
@@ -16,6 +16,7 @@
 import 'package:analyzer/src/generated/sdk.dart';
 import 'package:analyzer/src/test_utilities/mock_sdk.dart';
 import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
+import 'package:meta/meta.dart';
 import 'package:test/test.dart';
 
 import 'mocks.dart';
@@ -189,6 +190,7 @@
     handleSuccessfulRequest(request);
   }
 
+  @mustCallSuper
   void setUp() {
     serverChannel = MockServerChannel();
     projectPath = convertPath('/project');
@@ -205,6 +207,7 @@
     });
   }
 
+  @mustCallSuper
   void tearDown() {
     server.done();
     handler = null;
diff --git a/pkg/analysis_server/test/edit/bulk_fixes_test.dart b/pkg/analysis_server/test/edit/bulk_fixes_test.dart
index a845792..f22d513 100644
--- a/pkg/analysis_server/test/edit/bulk_fixes_test.dart
+++ b/pkg/analysis_server/test/edit/bulk_fixes_test.dart
@@ -6,9 +6,11 @@
 
 import 'package:analysis_server/protocol/protocol_generated.dart';
 import 'package:analysis_server/src/edit/edit_domain.dart';
+import 'package:analysis_server/src/services/linter/lint_names.dart';
 import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:linter/src/rules.dart';
+import 'package:meta/meta.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -22,6 +24,21 @@
 
 @reflectiveTest
 class BulkFixesTest extends AbstractAnalysisTest {
+  void assertContains(List<BulkFix> details,
+      {@required String path, @required String code, @required int count}) {
+    for (var detail in details) {
+      if (detail.path == path) {
+        for (var fix in detail.fixes) {
+          if (fix.code == code) {
+            expect(fix.occurrences, count);
+            return;
+          }
+        }
+      }
+    }
+    fail('No match found for: $path:$code->$count in $details');
+  }
+
   Future<void> assertEditEquals(String expectedSource) async {
     await waitForTasksFinished();
     var edits = await _getBulkEdits();
@@ -133,6 +150,40 @@
 ''');
   }
 
+  Future<void> test_details() async {
+    addAnalysisOptionsFile('''
+linter:
+  rules:
+    - annotate_overrides
+    - unnecessary_new
+''');
+
+    var fileA = convertPath('$projectPath/a.dart');
+    newFile(fileA, content: '''
+class A {
+  A f() => new A();
+}
+class B extends A {
+  A f() => new B();
+}
+''');
+
+    addTestFile('''
+import 'a.dart';
+
+A f() => new A();
+''');
+
+    var details = await _getBulkFixDetails();
+    expect(details, hasLength(2));
+    assertContains(details,
+        path: fileA, code: LintNames.unnecessary_new, count: 2);
+    assertContains(details,
+        path: fileA, code: LintNames.annotate_overrides, count: 1);
+    assertContains(details,
+        path: testFile, code: LintNames.unnecessary_new, count: 1);
+  }
+
   Future<void> test_unnecessaryNew() async {
     addAnalysisOptionsFile('''
 linter:
@@ -211,9 +262,18 @@
   }
 
   Future<List<SourceFileEdit>> _getBulkEdits() async {
+    var result = await _getBulkFixes();
+    return result.edits;
+  }
+
+  Future<List<BulkFix>> _getBulkFixDetails() async {
+    var result = await _getBulkFixes();
+    return result.details;
+  }
+
+  Future<EditBulkFixesResult> _getBulkFixes() async {
     var request = EditBulkFixesParams([projectPath]).toRequest('0');
     var response = await waitResponse(request);
-    var result = EditBulkFixesResult.fromResponse(response);
-    return result.edits;
+    return EditBulkFixesResult.fromResponse(response);
   }
 }
diff --git a/pkg/analysis_server/test/integration/support/integration_test_methods.dart b/pkg/analysis_server/test/integration/support/integration_test_methods.dart
index c6f966e..27c7687 100644
--- a/pkg/analysis_server/test/integration/support/integration_test_methods.dart
+++ b/pkg/analysis_server/test/integration/support/integration_test_methods.dart
@@ -1587,6 +1587,11 @@
   /// edits: List<SourceFileEdit>
   ///
   ///   A list of source edits to apply the recommended changes.
+  ///
+  /// details: List<BulkFix>
+  ///
+  ///   Details that summarize the fixes associated with the recommended
+  ///   changes.
   Future<EditBulkFixesResult> sendEditBulkFixes(List<String> included) async {
     var params = EditBulkFixesParams(included).toJson();
     var result = await server.send('edit.bulkFixes', params);
diff --git a/pkg/analysis_server/test/integration/support/protocol_matchers.dart b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
index 534a918..36c6e26f 100644
--- a/pkg/analysis_server/test/integration/support/protocol_matchers.dart
+++ b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
@@ -192,6 +192,24 @@
     'AvailableSuggestionSet',
     {'id': isInt, 'uri': isString, 'items': isListOf(isAvailableSuggestion)}));
 
+/// BulkFix
+///
+/// {
+///   "path": FilePath
+///   "fixes": List<BulkFixDetail>
+/// }
+final Matcher isBulkFix = LazyMatcher(() => MatchesJsonObject(
+    'BulkFix', {'path': isFilePath, 'fixes': isListOf(isBulkFixDetail)}));
+
+/// BulkFixDetail
+///
+/// {
+///   "code": String
+///   "occurrences": int
+/// }
+final Matcher isBulkFixDetail = LazyMatcher(() => MatchesJsonObject(
+    'BulkFixDetail', {'code': isString, 'occurrences': isInt}));
+
 /// ChangeContentOverlay
 ///
 /// {
@@ -2229,9 +2247,11 @@
 ///
 /// {
 ///   "edits": List<SourceFileEdit>
+///   "details": List<BulkFix>
 /// }
 final Matcher isEditBulkFixesResult = LazyMatcher(() => MatchesJsonObject(
-    'edit.bulkFixes result', {'edits': isListOf(isSourceFileEdit)}));
+    'edit.bulkFixes result',
+    {'edits': isListOf(isSourceFileEdit), 'details': isListOf(isBulkFix)}));
 
 /// edit.dartfix params
 ///
diff --git a/pkg/analysis_server/tool/spec/generated/java/types/BulkFix.java b/pkg/analysis_server/tool/spec/generated/java/types/BulkFix.java
new file mode 100644
index 0000000..5b62f16
--- /dev/null
+++ b/pkg/analysis_server/tool/spec/generated/java/types/BulkFix.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+ * for details. All rights reserved. Use of this source code is governed by a
+ * BSD-style license that can be found in the LICENSE file.
+ *
+ * This file has been automatically generated. Please do not edit it manually.
+ * To regenerate the file, use the script "pkg/analysis_server/tool/spec/generate_files".
+ */
+package org.dartlang.analysis.server.protocol;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import com.google.common.collect.Lists;
+import com.google.dart.server.utilities.general.JsonUtilities;
+import com.google.dart.server.utilities.general.ObjectUtilities;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import java.util.ArrayList;
+import java.util.Iterator;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * A description of bulk fixes to a library.
+ *
+ * @coverage dart.server.generated.types
+ */
+@SuppressWarnings("unused")
+public class BulkFix {
+
+  public static final BulkFix[] EMPTY_ARRAY = new BulkFix[0];
+
+  public static final List<BulkFix> EMPTY_LIST = Lists.newArrayList();
+
+  /**
+   * The path of the library.
+   */
+  private final String path;
+
+  /**
+   * A list of bulk fix details.
+   */
+  private final List<BulkFixDetail> fixes;
+
+  /**
+   * Constructor for {@link BulkFix}.
+   */
+  public BulkFix(String path, List<BulkFixDetail> fixes) {
+    this.path = path;
+    this.fixes = fixes;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof BulkFix) {
+      BulkFix other = (BulkFix) obj;
+      return
+        ObjectUtilities.equals(other.path, path) &&
+        ObjectUtilities.equals(other.fixes, fixes);
+    }
+    return false;
+  }
+
+  public static BulkFix fromJson(JsonObject jsonObject) {
+    String path = jsonObject.get("path").getAsString();
+    List<BulkFixDetail> fixes = BulkFixDetail.fromJsonArray(jsonObject.get("fixes").getAsJsonArray());
+    return new BulkFix(path, fixes);
+  }
+
+  public static List<BulkFix> fromJsonArray(JsonArray jsonArray) {
+    if (jsonArray == null) {
+      return EMPTY_LIST;
+    }
+    ArrayList<BulkFix> list = new ArrayList<BulkFix>(jsonArray.size());
+    Iterator<JsonElement> iterator = jsonArray.iterator();
+    while (iterator.hasNext()) {
+      list.add(fromJson(iterator.next().getAsJsonObject()));
+    }
+    return list;
+  }
+
+  /**
+   * A list of bulk fix details.
+   */
+  public List<BulkFixDetail> getFixes() {
+    return fixes;
+  }
+
+  /**
+   * The path of the library.
+   */
+  public String getPath() {
+    return path;
+  }
+
+  @Override
+  public int hashCode() {
+    HashCodeBuilder builder = new HashCodeBuilder();
+    builder.append(path);
+    builder.append(fixes);
+    return builder.toHashCode();
+  }
+
+  public JsonObject toJson() {
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("path", path);
+    JsonArray jsonArrayFixes = new JsonArray();
+    for (BulkFixDetail elt : fixes) {
+      jsonArrayFixes.add(elt.toJson());
+    }
+    jsonObject.add("fixes", jsonArrayFixes);
+    return jsonObject;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    builder.append("[");
+    builder.append("path=");
+    builder.append(path + ", ");
+    builder.append("fixes=");
+    builder.append(StringUtils.join(fixes, ", "));
+    builder.append("]");
+    return builder.toString();
+  }
+
+}
diff --git a/pkg/analysis_server/tool/spec/generated/java/types/BulkFixDetail.java b/pkg/analysis_server/tool/spec/generated/java/types/BulkFixDetail.java
new file mode 100644
index 0000000..098306f7
--- /dev/null
+++ b/pkg/analysis_server/tool/spec/generated/java/types/BulkFixDetail.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+ * for details. All rights reserved. Use of this source code is governed by a
+ * BSD-style license that can be found in the LICENSE file.
+ *
+ * This file has been automatically generated. Please do not edit it manually.
+ * To regenerate the file, use the script "pkg/analysis_server/tool/spec/generate_files".
+ */
+package org.dartlang.analysis.server.protocol;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import com.google.common.collect.Lists;
+import com.google.dart.server.utilities.general.JsonUtilities;
+import com.google.dart.server.utilities.general.ObjectUtilities;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import java.util.ArrayList;
+import java.util.Iterator;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * A description of a fix applied to a library.
+ *
+ * @coverage dart.server.generated.types
+ */
+@SuppressWarnings("unused")
+public class BulkFixDetail {
+
+  public static final BulkFixDetail[] EMPTY_ARRAY = new BulkFixDetail[0];
+
+  public static final List<BulkFixDetail> EMPTY_LIST = Lists.newArrayList();
+
+  /**
+   * The code of the diagnostic associated with the fix.
+   */
+  private final String code;
+
+  /**
+   * The number times the associated diagnostic was fixed in the associated source edit.
+   */
+  private final int occurrences;
+
+  /**
+   * Constructor for {@link BulkFixDetail}.
+   */
+  public BulkFixDetail(String code, int occurrences) {
+    this.code = code;
+    this.occurrences = occurrences;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof BulkFixDetail) {
+      BulkFixDetail other = (BulkFixDetail) obj;
+      return
+        ObjectUtilities.equals(other.code, code) &&
+        other.occurrences == occurrences;
+    }
+    return false;
+  }
+
+  public static BulkFixDetail fromJson(JsonObject jsonObject) {
+    String code = jsonObject.get("code").getAsString();
+    int occurrences = jsonObject.get("occurrences").getAsInt();
+    return new BulkFixDetail(code, occurrences);
+  }
+
+  public static List<BulkFixDetail> fromJsonArray(JsonArray jsonArray) {
+    if (jsonArray == null) {
+      return EMPTY_LIST;
+    }
+    ArrayList<BulkFixDetail> list = new ArrayList<BulkFixDetail>(jsonArray.size());
+    Iterator<JsonElement> iterator = jsonArray.iterator();
+    while (iterator.hasNext()) {
+      list.add(fromJson(iterator.next().getAsJsonObject()));
+    }
+    return list;
+  }
+
+  /**
+   * The code of the diagnostic associated with the fix.
+   */
+  public String getCode() {
+    return code;
+  }
+
+  /**
+   * The number times the associated diagnostic was fixed in the associated source edit.
+   */
+  public int getOccurrences() {
+    return occurrences;
+  }
+
+  @Override
+  public int hashCode() {
+    HashCodeBuilder builder = new HashCodeBuilder();
+    builder.append(code);
+    builder.append(occurrences);
+    return builder.toHashCode();
+  }
+
+  public JsonObject toJson() {
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("code", code);
+    jsonObject.addProperty("occurrences", occurrences);
+    return jsonObject;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    builder.append("[");
+    builder.append("code=");
+    builder.append(code + ", ");
+    builder.append("occurrences=");
+    builder.append(occurrences);
+    builder.append("]");
+    return builder.toString();
+  }
+
+}
diff --git a/pkg/analysis_server/tool/spec/spec_input.html b/pkg/analysis_server/tool/spec/spec_input.html
index 262ca7c..8b49fc8 100644
--- a/pkg/analysis_server/tool/spec/spec_input.html
+++ b/pkg/analysis_server/tool/spec/spec_input.html
@@ -7,7 +7,7 @@
 <body>
 <h1>Analysis Server API Specification</h1>
 <h1 style="color:#999999">Version
-  <version>1.31.0</version>
+  <version>1.32.0</version>
 </h1>
 <p>
   This document contains a specification of the API provided by the
@@ -2196,6 +2196,15 @@
           A list of source edits to apply the recommended changes.
         </p>
       </field>
+      <field name="details">
+        <list>
+          <ref>BulkFix</ref>
+        </list>
+        <p>
+          Details that summarize the fixes associated with the recommended
+          changes.
+        </p>
+      </field>
     </result>
   </request>
   <request method="dartfix" experimental="true">
@@ -3537,6 +3546,47 @@
       </field>
     </object>
   </type>
+  <type name="BulkFix">
+    <p>
+      A description of bulk fixes to a library.
+    </p>
+    <object>
+      <field name="path">
+        <ref>FilePath</ref>
+        <p>
+          The path of the library.
+        </p>
+      </field>
+      <field name="fixes">
+        <list>
+          <ref>BulkFixDetail</ref>
+        </list>
+        <p>
+          A list of bulk fix details.
+        </p>
+      </field>
+    </object>
+  </type>
+  <type name="BulkFixDetail">
+    <p>
+      A description of a fix applied to a library.
+    </p>
+    <object>
+      <field name="code">
+        <ref>String</ref>
+        <p>
+          The code of the diagnostic associated with the fix.
+        </p>
+      </field>
+      <field name="occurrences">
+        <ref>int</ref>
+        <p>
+          The number times the associated diagnostic was fixed in the associated
+          source edit.
+        </p>
+      </field>
+    </object>
+  </type>
   <type name="ClosingLabel">
     <p>
       A label that is associated with a range of code that may be useful to
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
index 61ada08..cdccff0 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
@@ -6,7 +6,7 @@
 // To regenerate the file, use the script
 // "pkg/analysis_server/tool/spec/generate_files".
 
-const String PROTOCOL_VERSION = '1.31.0';
+const String PROTOCOL_VERSION = '1.32.0';
 
 const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
 const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';
@@ -222,6 +222,7 @@
 const String EDIT_REQUEST_ORGANIZE_DIRECTIVES_FILE = 'file';
 const String EDIT_REQUEST_SORT_MEMBERS = 'edit.sortMembers';
 const String EDIT_REQUEST_SORT_MEMBERS_FILE = 'file';
+const String EDIT_RESPONSE_BULK_FIXES_DETAILS = 'details';
 const String EDIT_RESPONSE_BULK_FIXES_EDITS = 'edits';
 const String EDIT_RESPONSE_DARTFIX_DETAILS = 'details';
 const String EDIT_RESPONSE_DARTFIX_EDITS = 'edits';
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
index b6d697f..b9466f0 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
@@ -4955,6 +4955,188 @@
   }
 }
 
+/// BulkFix
+///
+/// {
+///   "path": FilePath
+///   "fixes": List<BulkFixDetail>
+/// }
+///
+/// Clients may not extend, implement or mix-in this class.
+class BulkFix implements HasToJson {
+  String _path;
+
+  List<BulkFixDetail> _fixes;
+
+  /// The path of the library.
+  String get path => _path;
+
+  /// The path of the library.
+  set path(String value) {
+    assert(value != null);
+    _path = value;
+  }
+
+  /// A list of bulk fix details.
+  List<BulkFixDetail> get fixes => _fixes;
+
+  /// A list of bulk fix details.
+  set fixes(List<BulkFixDetail> value) {
+    assert(value != null);
+    _fixes = value;
+  }
+
+  BulkFix(String path, List<BulkFixDetail> fixes) {
+    this.path = path;
+    this.fixes = fixes;
+  }
+
+  factory BulkFix.fromJson(
+      JsonDecoder jsonDecoder, String jsonPath, Object json) {
+    json ??= {};
+    if (json is Map) {
+      String path;
+      if (json.containsKey('path')) {
+        path = jsonDecoder.decodeString(jsonPath + '.path', json['path']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'path');
+      }
+      List<BulkFixDetail> fixes;
+      if (json.containsKey('fixes')) {
+        fixes = jsonDecoder.decodeList(
+            jsonPath + '.fixes',
+            json['fixes'],
+            (String jsonPath, Object json) =>
+                BulkFixDetail.fromJson(jsonDecoder, jsonPath, json));
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'fixes');
+      }
+      return BulkFix(path, fixes);
+    } else {
+      throw jsonDecoder.mismatch(jsonPath, 'BulkFix', json);
+    }
+  }
+
+  @override
+  Map<String, dynamic> toJson() {
+    var result = <String, dynamic>{};
+    result['path'] = path;
+    result['fixes'] =
+        fixes.map((BulkFixDetail value) => value.toJson()).toList();
+    return result;
+  }
+
+  @override
+  String toString() => json.encode(toJson());
+
+  @override
+  bool operator ==(other) {
+    if (other is BulkFix) {
+      return path == other.path &&
+          listEqual(
+              fixes, other.fixes, (BulkFixDetail a, BulkFixDetail b) => a == b);
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode {
+    var hash = 0;
+    hash = JenkinsSmiHash.combine(hash, path.hashCode);
+    hash = JenkinsSmiHash.combine(hash, fixes.hashCode);
+    return JenkinsSmiHash.finish(hash);
+  }
+}
+
+/// BulkFixDetail
+///
+/// {
+///   "code": String
+///   "occurrences": int
+/// }
+///
+/// Clients may not extend, implement or mix-in this class.
+class BulkFixDetail implements HasToJson {
+  String _code;
+
+  int _occurrences;
+
+  /// The code of the diagnostic associated with the fix.
+  String get code => _code;
+
+  /// The code of the diagnostic associated with the fix.
+  set code(String value) {
+    assert(value != null);
+    _code = value;
+  }
+
+  /// The number times the associated diagnostic was fixed in the associated
+  /// source edit.
+  int get occurrences => _occurrences;
+
+  /// The number times the associated diagnostic was fixed in the associated
+  /// source edit.
+  set occurrences(int value) {
+    assert(value != null);
+    _occurrences = value;
+  }
+
+  BulkFixDetail(String code, int occurrences) {
+    this.code = code;
+    this.occurrences = occurrences;
+  }
+
+  factory BulkFixDetail.fromJson(
+      JsonDecoder jsonDecoder, String jsonPath, Object json) {
+    json ??= {};
+    if (json is Map) {
+      String code;
+      if (json.containsKey('code')) {
+        code = jsonDecoder.decodeString(jsonPath + '.code', json['code']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'code');
+      }
+      int occurrences;
+      if (json.containsKey('occurrences')) {
+        occurrences = jsonDecoder.decodeInt(
+            jsonPath + '.occurrences', json['occurrences']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'occurrences');
+      }
+      return BulkFixDetail(code, occurrences);
+    } else {
+      throw jsonDecoder.mismatch(jsonPath, 'BulkFixDetail', json);
+    }
+  }
+
+  @override
+  Map<String, dynamic> toJson() {
+    var result = <String, dynamic>{};
+    result['code'] = code;
+    result['occurrences'] = occurrences;
+    return result;
+  }
+
+  @override
+  String toString() => json.encode(toJson());
+
+  @override
+  bool operator ==(other) {
+    if (other is BulkFixDetail) {
+      return code == other.code && occurrences == other.occurrences;
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode {
+    var hash = 0;
+    hash = JenkinsSmiHash.combine(hash, code.hashCode);
+    hash = JenkinsSmiHash.combine(hash, occurrences.hashCode);
+    return JenkinsSmiHash.finish(hash);
+  }
+}
+
 /// ClosingLabel
 ///
 /// {
@@ -7210,12 +7392,15 @@
 ///
 /// {
 ///   "edits": List<SourceFileEdit>
+///   "details": List<BulkFix>
 /// }
 ///
 /// Clients may not extend, implement or mix-in this class.
 class EditBulkFixesResult implements ResponseResult {
   List<SourceFileEdit> _edits;
 
+  List<BulkFix> _details;
+
   /// A list of source edits to apply the recommended changes.
   List<SourceFileEdit> get edits => _edits;
 
@@ -7225,8 +7410,18 @@
     _edits = value;
   }
 
-  EditBulkFixesResult(List<SourceFileEdit> edits) {
+  /// Details that summarize the fixes associated with the recommended changes.
+  List<BulkFix> get details => _details;
+
+  /// Details that summarize the fixes associated with the recommended changes.
+  set details(List<BulkFix> value) {
+    assert(value != null);
+    _details = value;
+  }
+
+  EditBulkFixesResult(List<SourceFileEdit> edits, List<BulkFix> details) {
     this.edits = edits;
+    this.details = details;
   }
 
   factory EditBulkFixesResult.fromJson(
@@ -7243,7 +7438,17 @@
       } else {
         throw jsonDecoder.mismatch(jsonPath, 'edits');
       }
-      return EditBulkFixesResult(edits);
+      List<BulkFix> details;
+      if (json.containsKey('details')) {
+        details = jsonDecoder.decodeList(
+            jsonPath + '.details',
+            json['details'],
+            (String jsonPath, Object json) =>
+                BulkFix.fromJson(jsonDecoder, jsonPath, json));
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'details');
+      }
+      return EditBulkFixesResult(edits, details);
     } else {
       throw jsonDecoder.mismatch(jsonPath, 'edit.bulkFixes result', json);
     }
@@ -7261,6 +7466,7 @@
     var result = <String, dynamic>{};
     result['edits'] =
         edits.map((SourceFileEdit value) => value.toJson()).toList();
+    result['details'] = details.map((BulkFix value) => value.toJson()).toList();
     return result;
   }
 
@@ -7275,8 +7481,9 @@
   @override
   bool operator ==(other) {
     if (other is EditBulkFixesResult) {
-      return listEqual(
-          edits, other.edits, (SourceFileEdit a, SourceFileEdit b) => a == b);
+      return listEqual(edits, other.edits,
+              (SourceFileEdit a, SourceFileEdit b) => a == b) &&
+          listEqual(details, other.details, (BulkFix a, BulkFix b) => a == b);
     }
     return false;
   }
@@ -7285,6 +7492,7 @@
   int get hashCode {
     var hash = 0;
     hash = JenkinsSmiHash.combine(hash, edits.hashCode);
+    hash = JenkinsSmiHash.combine(hash, details.hashCode);
     return JenkinsSmiHash.finish(hash);
   }
 }
diff --git a/pkg/analyzer/test/src/summary/resynthesize_common.dart b/pkg/analyzer/test/src/summary/resynthesize_common.dart
index f55891d..6b7b5be 100644
--- a/pkg/analyzer/test/src/summary/resynthesize_common.dart
+++ b/pkg/analyzer/test/src/summary/resynthesize_common.dart
@@ -8194,69 +8194,141 @@
 ''');
   }
 
-  test_instanceInference_operator_equal_legacy() async {
-    var library = await checkLibrary(r'''
-class A1 {
+  test_instanceInference_operator_equal_legacy_from_legacy() async {
+    addLibrarySource('/legacy.dart', r'''
+// @dart = 2.7
+class LegacyDefault {
   bool operator==(other) => false;
 }
-class A2 {
+class LegacyObject {
+  bool operator==(Object other) => false;
+}
+class LegacyInt {
   bool operator==(int other) => false;
 }
-class B1 extends A1 {
+''');
+    var library = await checkLibrary(r'''
+import 'legacy.dart';
+class X1 extends LegacyDefault  {
   bool operator==(other) => false;
 }
-class B2 extends A2 {
+class X2 extends LegacyObject {
+  bool operator==(other) => false;
+}
+class X3 extends LegacyInt {
   bool operator==(other) => false;
 }
 ''');
     checkElementText(
         library,
         r'''
-class A1 {
+import 'legacy.dart';
+class X1 extends LegacyDefault* {
   bool* ==(dynamic other) {}
 }
-class A2 {
-  bool* ==(int* other) {}
+class X2 extends LegacyObject* {
+  bool* ==(Object* other) {}
 }
-class B1 extends A1* {
-  bool* ==(dynamic other) {}
-}
-class B2 extends A2* {
+class X3 extends LegacyInt* {
   bool* ==(int* other) {}
 }
 ''',
         annotateNullability: true);
   }
 
-  test_instanceInference_operator_equal_nnbd() async {
+  test_instanceInference_operator_equal_legacy_from_legacy_nullSafe() async {
     featureSet = enableNnbd;
-    var library = await checkLibrary(r'''
-class A1 {
+    addLibrarySource('/legacy.dart', r'''
+// @dart = 2.7
+class LegacyDefault {
   bool operator==(other) => false;
 }
-class A2 {
+class LegacyObject {
+  bool operator==(Object other) => false;
+}
+class LegacyInt {
   bool operator==(int other) => false;
 }
-class B1 extends A1 {
+''');
+    addLibrarySource('/nullSafe.dart', r'''
+class NullSafeDefault {
   bool operator==(other) => false;
 }
-class B2 extends A2 {
+class NullSafeObject {
+  bool operator==(Object other) => false;
+}
+class NullSafeInt {
+  bool operator==(int other) => false;
+}
+''');
+    var library = await checkLibrary(r'''
+// @dart = 2.7
+import 'legacy.dart';
+import 'nullSafe.dart';
+class X1 extends LegacyDefault implements NullSafeDefault {
+  bool operator==(other) => false;
+}
+class X2 extends LegacyObject implements NullSafeObject {
+  bool operator==(other) => false;
+}
+class X3 extends LegacyInt implements NullSafeInt {
   bool operator==(other) => false;
 }
 ''');
     checkElementText(
         library,
         r'''
-class A1 {
+import 'legacy.dart';
+import 'nullSafe.dart';
+class X1 extends LegacyDefault* implements NullSafeDefault* {
+  bool* ==(dynamic other) {}
+}
+class X2 extends LegacyObject* implements NullSafeObject* {
+  bool* ==(Object* other) {}
+}
+class X3 extends LegacyInt* implements NullSafeInt* {
+  bool* ==(int* other) {}
+}
+''',
+        annotateNullability: true);
+  }
+
+  test_instanceInference_operator_equal_nullSafe_from_nullSafe() async {
+    featureSet = enableNnbd;
+    addLibrarySource('/nullSafe.dart', r'''
+class NullSafeDefault {
+  bool operator==(other) => false;
+}
+class NullSafeObject {
+  bool operator==(Object other) => false;
+}
+class NullSafeInt {
+  bool operator==(int other) => false;
+}
+''');
+    var library = await checkLibrary(r'''
+import 'nullSafe.dart';
+class X1 extends NullSafeDefault {
+  bool operator==(other) => false;
+}
+class X2 extends NullSafeObject {
+  bool operator==(other) => false;
+}
+class X3 extends NullSafeInt {
+  bool operator==(other) => false;
+}
+''');
+    checkElementText(
+        library,
+        r'''
+import 'nullSafe.dart';
+class X1 extends NullSafeDefault {
   bool ==(Object other) {}
 }
-class A2 {
-  bool ==(int other) {}
-}
-class B1 extends A1 {
+class X2 extends NullSafeObject {
   bool ==(Object other) {}
 }
-class B2 extends A2 {
+class X3 extends NullSafeInt {
   bool ==(int other) {}
 }
 ''',
diff --git a/pkg/dartdev/lib/src/commands/fix.dart b/pkg/dartdev/lib/src/commands/fix.dart
index 3419c75..11dbee4 100644
--- a/pkg/dartdev/lib/src/commands/fix.dart
+++ b/pkg/dartdev/lib/src/commands/fix.dart
@@ -16,7 +16,7 @@
 class FixCommand extends DartdevCommand {
   static const String cmdName = 'fix';
 
-  // This command is hidden as its currently experimental.
+  // This command is hidden as it's currently experimental.
   FixCommand() : super(cmdName, 'Fix Dart source code.', hidden: true) {
     argParser.addFlag('dry-run',
         abbr: 'n',
@@ -41,8 +41,10 @@
       usageException("Directory doesn't exist: ${dir.path}");
     }
 
+    var modeText = dryRun ? ' (dry run)' : '';
+
     var progress = log.progress(
-        'Computing fixes in ${path.basename(path.canonicalize(dir.path))}');
+        'Computing fixes in ${path.basename(path.canonicalize(dir.path))}$modeText');
 
     var server = AnalysisServer(
       io.Directory(sdk.sdkPath),
@@ -71,16 +73,30 @@
       log.stdout('Nothing to fix!');
     } else {
       if (dryRun) {
-        log.stdout("Running 'dart fix' without '--dry-run' would apply changes "
-            'in the following files:');
-        var files = <String>{};
-        for (var edit in edits) {
-          var file = edit.file;
-          files.add(path.relative(file, from: dir.path));
-        }
-        var paths = files.toList()..sort();
-        for (var path in paths) {
-          log.stdout(path);
+        var details = fixes.details;
+        details.sort((f1, f2) => path
+            .relative(f1.path, from: dir.path)
+            .compareTo(path.relative(f2.path, from: dir.path)));
+
+        var fileCount = 0;
+        var fixCount = 0;
+
+        details.forEach((d) {
+          ++fileCount;
+          d.fixes.forEach((f) {
+            fixCount += f.occurrences;
+          });
+        });
+
+        log.stdout(
+            '\n$fixCount proposed ${_pluralFix(fixCount)} in $fileCount ${pluralize("file", fileCount)}.\n');
+
+        for (var detail in details) {
+          log.stdout(path.relative(detail.path, from: dir.path));
+          for (var fix in detail.fixes) {
+            log.stdout(
+                '  ${fix.code} • ${fix.occurrences} ${_pluralFix(fix.occurrences)}');
+          }
         }
       } else {
         progress = log.progress('Applying fixes');
@@ -107,4 +123,6 @@
     }
     return files.length;
   }
+
+  String _pluralFix(int count) => count == 1 ? 'fix' : 'fixes';
 }
diff --git a/pkg/dartdev/test/commands/fix_test.dart b/pkg/dartdev/test/commands/fix_test.dart
index 5450742..101edbf 100644
--- a/pkg/dartdev/test/commands/fix_test.dart
+++ b/pkg/dartdev/test/commands/fix_test.dart
@@ -47,22 +47,28 @@
   test('dry-run', () {
     p = project(
       mainSrc: '''
-var x = "";
+class A { 
+  String a() => "";
+}
+
+class B extends A {
+  String a() => "";
+}
 ''',
       analysisOptions: '''
 linter:
   rules:
+    - annotate_overrides  
     - prefer_single_quotes
 ''',
     );
     var result = p.runSync('fix', ['--dry-run', '.'], workingDir: p.dirPath);
     expect(result.exitCode, 0);
     expect(result.stderr, isEmpty);
-    expect(
-        result.stdout,
-        contains("Running 'dart fix' without '--dry-run' "
-            'would apply changes in the following files:'));
+    expect(result.stdout, contains('3 proposed fixes in 1 file.'));
     expect(result.stdout, contains('lib${Platform.pathSeparator}main.dart'));
+    expect(result.stdout, contains('  annotate_overrides • 1 fix'));
+    expect(result.stdout, contains('  prefer_single_quotes • 2 fixes'));
   });
 
   test('.', () {
diff --git a/pkg/front_end/lib/src/fasta/builder/field_builder.dart b/pkg/front_end/lib/src/fasta/builder/field_builder.dart
index e132a2d..85cca3c 100644
--- a/pkg/front_end/lib/src/fasta/builder/field_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/field_builder.dart
@@ -811,6 +811,7 @@
       ..isInternalImplementation = true;
     switch (_isSetStrategy) {
       case late_lowering.IsSetStrategy.useSentinelOrNull:
+      case late_lowering.IsSetStrategy.forceUseSentinel:
         // [_lateIsSetField] is never needed.
         break;
       case late_lowering.IsSetStrategy.forceUseIsSetField:
diff --git a/pkg/front_end/lib/src/fasta/kernel/late_lowering.dart b/pkg/front_end/lib/src/fasta/kernel/late_lowering.dart
index f1b548e..74ca5c1 100644
--- a/pkg/front_end/lib/src/fasta/kernel/late_lowering.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/late_lowering.dart
@@ -538,6 +538,11 @@
   /// been initialized.
   forceUseIsSetField,
 
+  /// Always use `createSentinel`and `isSentinel` from `dart:_internal` to
+  /// generate and check a sentinel value to signal an uninitialized
+  /// field/local.
+  forceUseSentinel,
+
   /// For potentially nullable fields/locals use an `isSet` field/local to track
   /// whether the field/local has been initialized. Otherwise use `null` as
   /// sentinel value to signal an uninitialized field/local.
@@ -557,7 +562,13 @@
 IsSetStrategy computeIsSetStrategy(SourceLibraryBuilder libraryBuilder) {
   IsSetStrategy isSetStrategy = IsSetStrategy.useIsSetFieldOrNull;
   if (libraryBuilder.loader.target.backendTarget.supportsLateLoweringSentinel) {
-    isSetStrategy = IsSetStrategy.useSentinelOrNull;
+    if (libraryBuilder.loader.nnbdMode != NnbdMode.Strong) {
+      // Non-nullable fields/locals might contain `null` so we always use the
+      // sentinel.
+      isSetStrategy = IsSetStrategy.forceUseSentinel;
+    } else {
+      isSetStrategy = IsSetStrategy.useSentinelOrNull;
+    }
   } else if (libraryBuilder.loader.nnbdMode != NnbdMode.Strong) {
     isSetStrategy = IsSetStrategy.forceUseIsSetField;
   }
@@ -568,6 +579,8 @@
   switch (isSetStrategy) {
     case IsSetStrategy.forceUseIsSetField:
       return IsSetEncoding.useIsSetField;
+    case IsSetStrategy.forceUseSentinel:
+      return IsSetEncoding.useSentinel;
     case IsSetStrategy.useIsSetFieldOrNull:
       return type.isPotentiallyNullable
           ? IsSetEncoding.useIsSetField
diff --git a/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart b/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart
index 3434c11..fac3e32 100644
--- a/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart
+++ b/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart
@@ -10,5 +10,6 @@
 late final int nonNullableFinalTopLevelField;
 late final int? nullableFinalTopLevelFieldWithInitializer = null;
 late final int nonNullableFinalTopLevelFieldWithInitializer = 0;
+late Never neverTopLevelField;
 
 main() {}
diff --git a/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.outline.expect b/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.outline.expect
index 98525c7..f88db33 100644
--- a/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.outline.expect
+++ b/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.outline.expect
@@ -10,6 +10,7 @@
 static field core::int? _#nonNullableFinalTopLevelField;
 static field core::int? _#nullableFinalTopLevelFieldWithInitializer;
 static field core::int? _#nonNullableFinalTopLevelFieldWithInitializer;
+static field Never? _#neverTopLevelField;
 static get nullableTopLevelField() → core::int?;
 static set nullableTopLevelField(core::int? #t1) → void;
 static get nonNullableTopLevelField() → core::int;
@@ -24,5 +25,7 @@
 static set nonNullableFinalTopLevelField(core::int #t6) → void;
 static get nullableFinalTopLevelFieldWithInitializer() → core::int?;
 static get nonNullableFinalTopLevelFieldWithInitializer() → core::int;
+static get neverTopLevelField() → Never;
+static set neverTopLevelField(Never #t7) → void;
 static method main() → dynamic
   ;
diff --git a/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.strong.expect b/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.strong.expect
index 5b254e4..007c84a 100644
--- a/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.strong.expect
+++ b/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.strong.expect
@@ -11,6 +11,7 @@
 static field core::int? _#nonNullableFinalTopLevelField = null;
 static field core::int? _#nullableFinalTopLevelFieldWithInitializer = _in::createSentinel<core::int?>();
 static field core::int? _#nonNullableFinalTopLevelFieldWithInitializer = null;
+static field Never? _#neverTopLevelField = null;
 static get nullableTopLevelField() → core::int?
   return let final core::int? #t1 = self::_#nullableTopLevelField in _in::isSentinel(#t1) ?{core::int?} throw new _in::LateError::fieldNI("nullableTopLevelField") : #t1{core::int?};
 static set nullableTopLevelField(core::int? #t2) → void
@@ -45,4 +46,8 @@
   return let final core::int? #t13 = self::_#nullableFinalTopLevelFieldWithInitializer in _in::isSentinel(#t13) ?{core::int?} let final core::int? #t14 = null in _in::isSentinel(self::_#nullableFinalTopLevelFieldWithInitializer) ?{core::int?} self::_#nullableFinalTopLevelFieldWithInitializer = #t14 : throw new _in::LateError::fieldADI("nullableFinalTopLevelFieldWithInitializer") : #t13;
 static get nonNullableFinalTopLevelFieldWithInitializer() → core::int
   return let final core::int? #t15 = self::_#nonNullableFinalTopLevelFieldWithInitializer in #t15.==(null) ?{core::int} let final core::int #t16 = 0 in self::_#nonNullableFinalTopLevelFieldWithInitializer.==(null) ?{core::int} self::_#nonNullableFinalTopLevelFieldWithInitializer = #t16 : throw new _in::LateError::fieldADI("nonNullableFinalTopLevelFieldWithInitializer") : #t15{core::int};
+static get neverTopLevelField() → Never
+  return let final Never? #t17 = self::_#neverTopLevelField in #t17.==(null) ?{Never} throw new _in::LateError::fieldNI("neverTopLevelField") : #t17{Never};
+static set neverTopLevelField(Never #t18) → void
+  self::_#neverTopLevelField = #t18;
 static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.strong.transformed.expect b/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.strong.transformed.expect
index 0a63f7d..6713a38 100644
--- a/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.strong.transformed.expect
@@ -11,6 +11,7 @@
 static field core::int? _#nonNullableFinalTopLevelField = null;
 static field core::int? _#nullableFinalTopLevelFieldWithInitializer = _in::createSentinel<core::int?>();
 static field core::int? _#nonNullableFinalTopLevelFieldWithInitializer = null;
+static field Never? _#neverTopLevelField = null;
 static get nullableTopLevelField() → core::int?
   return let final core::int? #t1 = self::_#nullableTopLevelField in _in::isSentinel(#t1) ?{core::int?} throw new _in::LateError::fieldNI("nullableTopLevelField") : #t1{core::int?};
 static set nullableTopLevelField(core::int? #t2) → void
@@ -45,10 +46,14 @@
   return let final core::int? #t13 = self::_#nullableFinalTopLevelFieldWithInitializer in _in::isSentinel(#t13) ?{core::int?} let final core::int? #t14 = null in _in::isSentinel(self::_#nullableFinalTopLevelFieldWithInitializer) ?{core::int?} self::_#nullableFinalTopLevelFieldWithInitializer = #t14 : throw new _in::LateError::fieldADI("nullableFinalTopLevelFieldWithInitializer") : #t13;
 static get nonNullableFinalTopLevelFieldWithInitializer() → core::int
   return let final core::int? #t15 = self::_#nonNullableFinalTopLevelFieldWithInitializer in #t15.==(null) ?{core::int} let final core::int #t16 = 0 in self::_#nonNullableFinalTopLevelFieldWithInitializer.==(null) ?{core::int} self::_#nonNullableFinalTopLevelFieldWithInitializer = #t16 : throw new _in::LateError::fieldADI("nonNullableFinalTopLevelFieldWithInitializer") : #t15{core::int};
+static get neverTopLevelField() → Never
+  return let final Never? #t17 = self::_#neverTopLevelField in #t17.==(null) ?{Never} throw new _in::LateError::fieldNI("neverTopLevelField") : #t17{Never};
+static set neverTopLevelField(Never #t18) → void
+  self::_#neverTopLevelField = #t18;
 static method main() → dynamic {}
 
 
 Extra constant evaluation status:
 Evaluated: VariableGet @ org-dartlang-testcase:///late_fields.dart:11:17 -> NullConstant(null)
 Evaluated: VariableGet @ org-dartlang-testcase:///late_fields.dart:12:16 -> IntConstant(0)
-Extra constant evaluation: evaluated: 98, effectively constant: 2
+Extra constant evaluation: evaluated: 108, effectively constant: 2
diff --git a/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.textual_outline.expect b/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.textual_outline.expect
index 34e9a83..521d4d6 100644
--- a/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.textual_outline.expect
+++ b/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.textual_outline.expect
@@ -16,4 +16,6 @@
 final int? nullableFinalTopLevelFieldWithInitializer = null;
 late ;
 final int nonNullableFinalTopLevelFieldWithInitializer = 0;
+late Never ;
+neverTopLevelField;
 main() {}
diff --git a/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.weak.expect b/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.weak.expect
index 5b254e4..c037d72 100644
--- a/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.weak.expect
+++ b/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.weak.expect
@@ -4,19 +4,20 @@
 import "dart:_internal" as _in;
 
 static field core::int? _#nullableTopLevelField = _in::createSentinel<core::int?>();
-static field core::int? _#nonNullableTopLevelField = null;
+static field core::int? _#nonNullableTopLevelField = _in::createSentinel<core::int>();
 static field core::int? _#nullableTopLevelFieldWithInitializer = _in::createSentinel<core::int?>();
-static field core::int? _#nonNullableTopLevelFieldWithInitializer = null;
+static field core::int? _#nonNullableTopLevelFieldWithInitializer = _in::createSentinel<core::int>();
 static field core::int? _#nullableFinalTopLevelField = _in::createSentinel<core::int?>();
-static field core::int? _#nonNullableFinalTopLevelField = null;
+static field core::int? _#nonNullableFinalTopLevelField = _in::createSentinel<core::int>();
 static field core::int? _#nullableFinalTopLevelFieldWithInitializer = _in::createSentinel<core::int?>();
-static field core::int? _#nonNullableFinalTopLevelFieldWithInitializer = null;
+static field core::int? _#nonNullableFinalTopLevelFieldWithInitializer = _in::createSentinel<core::int>();
+static field Never? _#neverTopLevelField = _in::createSentinel<Never>();
 static get nullableTopLevelField() → core::int?
   return let final core::int? #t1 = self::_#nullableTopLevelField in _in::isSentinel(#t1) ?{core::int?} throw new _in::LateError::fieldNI("nullableTopLevelField") : #t1{core::int?};
 static set nullableTopLevelField(core::int? #t2) → void
   self::_#nullableTopLevelField = #t2;
 static get nonNullableTopLevelField() → core::int
-  return let final core::int? #t3 = self::_#nonNullableTopLevelField in #t3.==(null) ?{core::int} throw new _in::LateError::fieldNI("nonNullableTopLevelField") : #t3{core::int};
+  return let final core::int? #t3 = self::_#nonNullableTopLevelField in _in::isSentinel(#t3) ?{core::int} throw new _in::LateError::fieldNI("nonNullableTopLevelField") : #t3{core::int};
 static set nonNullableTopLevelField(core::int #t4) → void
   self::_#nonNullableTopLevelField = #t4;
 static get nullableTopLevelFieldWithInitializer() → core::int?
@@ -24,7 +25,7 @@
 static set nullableTopLevelFieldWithInitializer(core::int? #t6) → void
   self::_#nullableTopLevelFieldWithInitializer = #t6;
 static get nonNullableTopLevelFieldWithInitializer() → core::int
-  return let final core::int? #t7 = self::_#nonNullableTopLevelFieldWithInitializer in #t7.==(null) ?{core::int} self::_#nonNullableTopLevelFieldWithInitializer = 0 : #t7{core::int};
+  return let final core::int? #t7 = self::_#nonNullableTopLevelFieldWithInitializer in _in::isSentinel(#t7) ?{core::int} self::_#nonNullableTopLevelFieldWithInitializer = 0 : #t7{core::int};
 static set nonNullableTopLevelFieldWithInitializer(core::int #t8) → void
   self::_#nonNullableTopLevelFieldWithInitializer = #t8;
 static get nullableFinalTopLevelField() → core::int?
@@ -35,14 +36,18 @@
   else
     throw new _in::LateError::fieldAI("nullableFinalTopLevelField");
 static get nonNullableFinalTopLevelField() → core::int
-  return let final core::int? #t11 = self::_#nonNullableFinalTopLevelField in #t11.==(null) ?{core::int} throw new _in::LateError::fieldNI("nonNullableFinalTopLevelField") : #t11{core::int};
+  return let final core::int? #t11 = self::_#nonNullableFinalTopLevelField in _in::isSentinel(#t11) ?{core::int} throw new _in::LateError::fieldNI("nonNullableFinalTopLevelField") : #t11{core::int};
 static set nonNullableFinalTopLevelField(core::int #t12) → void
-  if(self::_#nonNullableFinalTopLevelField.==(null))
+  if(_in::isSentinel(self::_#nonNullableFinalTopLevelField))
     self::_#nonNullableFinalTopLevelField = #t12;
   else
     throw new _in::LateError::fieldAI("nonNullableFinalTopLevelField");
 static get nullableFinalTopLevelFieldWithInitializer() → core::int?
   return let final core::int? #t13 = self::_#nullableFinalTopLevelFieldWithInitializer in _in::isSentinel(#t13) ?{core::int?} let final core::int? #t14 = null in _in::isSentinel(self::_#nullableFinalTopLevelFieldWithInitializer) ?{core::int?} self::_#nullableFinalTopLevelFieldWithInitializer = #t14 : throw new _in::LateError::fieldADI("nullableFinalTopLevelFieldWithInitializer") : #t13;
 static get nonNullableFinalTopLevelFieldWithInitializer() → core::int
-  return let final core::int? #t15 = self::_#nonNullableFinalTopLevelFieldWithInitializer in #t15.==(null) ?{core::int} let final core::int #t16 = 0 in self::_#nonNullableFinalTopLevelFieldWithInitializer.==(null) ?{core::int} self::_#nonNullableFinalTopLevelFieldWithInitializer = #t16 : throw new _in::LateError::fieldADI("nonNullableFinalTopLevelFieldWithInitializer") : #t15{core::int};
+  return let final core::int #t15 = self::_#nonNullableFinalTopLevelFieldWithInitializer in _in::isSentinel(#t15) ?{core::int} let final core::int #t16 = 0 in _in::isSentinel(self::_#nonNullableFinalTopLevelFieldWithInitializer) ?{core::int} self::_#nonNullableFinalTopLevelFieldWithInitializer = #t16 : throw new _in::LateError::fieldADI("nonNullableFinalTopLevelFieldWithInitializer") : #t15;
+static get neverTopLevelField() → Never
+  return let final Never? #t17 = self::_#neverTopLevelField in _in::isSentinel(#t17) ?{Never} throw new _in::LateError::fieldNI("neverTopLevelField") : #t17{Never};
+static set neverTopLevelField(Never #t18) → void
+  self::_#neverTopLevelField = #t18;
 static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.weak.transformed.expect b/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.weak.transformed.expect
index 0a63f7d..24976f8 100644
--- a/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/late_lowering_sentinel/late_fields.dart.weak.transformed.expect
@@ -4,19 +4,20 @@
 import "dart:_internal" as _in;
 
 static field core::int? _#nullableTopLevelField = _in::createSentinel<core::int?>();
-static field core::int? _#nonNullableTopLevelField = null;
+static field core::int? _#nonNullableTopLevelField = _in::createSentinel<core::int>();
 static field core::int? _#nullableTopLevelFieldWithInitializer = _in::createSentinel<core::int?>();
-static field core::int? _#nonNullableTopLevelFieldWithInitializer = null;
+static field core::int? _#nonNullableTopLevelFieldWithInitializer = _in::createSentinel<core::int>();
 static field core::int? _#nullableFinalTopLevelField = _in::createSentinel<core::int?>();
-static field core::int? _#nonNullableFinalTopLevelField = null;
+static field core::int? _#nonNullableFinalTopLevelField = _in::createSentinel<core::int>();
 static field core::int? _#nullableFinalTopLevelFieldWithInitializer = _in::createSentinel<core::int?>();
-static field core::int? _#nonNullableFinalTopLevelFieldWithInitializer = null;
+static field core::int? _#nonNullableFinalTopLevelFieldWithInitializer = _in::createSentinel<core::int>();
+static field Never? _#neverTopLevelField = _in::createSentinel<Never>();
 static get nullableTopLevelField() → core::int?
   return let final core::int? #t1 = self::_#nullableTopLevelField in _in::isSentinel(#t1) ?{core::int?} throw new _in::LateError::fieldNI("nullableTopLevelField") : #t1{core::int?};
 static set nullableTopLevelField(core::int? #t2) → void
   self::_#nullableTopLevelField = #t2;
 static get nonNullableTopLevelField() → core::int
-  return let final core::int? #t3 = self::_#nonNullableTopLevelField in #t3.==(null) ?{core::int} throw new _in::LateError::fieldNI("nonNullableTopLevelField") : #t3{core::int};
+  return let final core::int? #t3 = self::_#nonNullableTopLevelField in _in::isSentinel(#t3) ?{core::int} throw new _in::LateError::fieldNI("nonNullableTopLevelField") : #t3{core::int};
 static set nonNullableTopLevelField(core::int #t4) → void
   self::_#nonNullableTopLevelField = #t4;
 static get nullableTopLevelFieldWithInitializer() → core::int?
@@ -24,7 +25,7 @@
 static set nullableTopLevelFieldWithInitializer(core::int? #t6) → void
   self::_#nullableTopLevelFieldWithInitializer = #t6;
 static get nonNullableTopLevelFieldWithInitializer() → core::int
-  return let final core::int? #t7 = self::_#nonNullableTopLevelFieldWithInitializer in #t7.==(null) ?{core::int} self::_#nonNullableTopLevelFieldWithInitializer = 0 : #t7{core::int};
+  return let final core::int? #t7 = self::_#nonNullableTopLevelFieldWithInitializer in _in::isSentinel(#t7) ?{core::int} self::_#nonNullableTopLevelFieldWithInitializer = 0 : #t7{core::int};
 static set nonNullableTopLevelFieldWithInitializer(core::int #t8) → void
   self::_#nonNullableTopLevelFieldWithInitializer = #t8;
 static get nullableFinalTopLevelField() → core::int?
@@ -35,20 +36,24 @@
   else
     throw new _in::LateError::fieldAI("nullableFinalTopLevelField");
 static get nonNullableFinalTopLevelField() → core::int
-  return let final core::int? #t11 = self::_#nonNullableFinalTopLevelField in #t11.==(null) ?{core::int} throw new _in::LateError::fieldNI("nonNullableFinalTopLevelField") : #t11{core::int};
+  return let final core::int? #t11 = self::_#nonNullableFinalTopLevelField in _in::isSentinel(#t11) ?{core::int} throw new _in::LateError::fieldNI("nonNullableFinalTopLevelField") : #t11{core::int};
 static set nonNullableFinalTopLevelField(core::int #t12) → void
-  if(self::_#nonNullableFinalTopLevelField.==(null))
+  if(_in::isSentinel(self::_#nonNullableFinalTopLevelField))
     self::_#nonNullableFinalTopLevelField = #t12;
   else
     throw new _in::LateError::fieldAI("nonNullableFinalTopLevelField");
 static get nullableFinalTopLevelFieldWithInitializer() → core::int?
   return let final core::int? #t13 = self::_#nullableFinalTopLevelFieldWithInitializer in _in::isSentinel(#t13) ?{core::int?} let final core::int? #t14 = null in _in::isSentinel(self::_#nullableFinalTopLevelFieldWithInitializer) ?{core::int?} self::_#nullableFinalTopLevelFieldWithInitializer = #t14 : throw new _in::LateError::fieldADI("nullableFinalTopLevelFieldWithInitializer") : #t13;
 static get nonNullableFinalTopLevelFieldWithInitializer() → core::int
-  return let final core::int? #t15 = self::_#nonNullableFinalTopLevelFieldWithInitializer in #t15.==(null) ?{core::int} let final core::int #t16 = 0 in self::_#nonNullableFinalTopLevelFieldWithInitializer.==(null) ?{core::int} self::_#nonNullableFinalTopLevelFieldWithInitializer = #t16 : throw new _in::LateError::fieldADI("nonNullableFinalTopLevelFieldWithInitializer") : #t15{core::int};
+  return let final core::int #t15 = self::_#nonNullableFinalTopLevelFieldWithInitializer in _in::isSentinel(#t15) ?{core::int} let final core::int #t16 = 0 in _in::isSentinel(self::_#nonNullableFinalTopLevelFieldWithInitializer) ?{core::int} self::_#nonNullableFinalTopLevelFieldWithInitializer = #t16 : throw new _in::LateError::fieldADI("nonNullableFinalTopLevelFieldWithInitializer") : #t15;
+static get neverTopLevelField() → Never
+  return let final Never? #t17 = self::_#neverTopLevelField in _in::isSentinel(#t17) ?{Never} throw new _in::LateError::fieldNI("neverTopLevelField") : #t17{Never};
+static set neverTopLevelField(Never #t18) → void
+  self::_#neverTopLevelField = #t18;
 static method main() → dynamic {}
 
 
 Extra constant evaluation status:
 Evaluated: VariableGet @ org-dartlang-testcase:///late_fields.dart:11:17 -> NullConstant(null)
 Evaluated: VariableGet @ org-dartlang-testcase:///late_fields.dart:12:16 -> IntConstant(0)
-Extra constant evaluation: evaluated: 98, effectively constant: 2
+Extra constant evaluation: evaluated: 113, effectively constant: 2
diff --git a/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart b/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart
index b0799a6..2f15de4 100644
--- a/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart
+++ b/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart
@@ -11,6 +11,7 @@
   late final int nonNullableFinalTopLevelLocal;
   late final int? nullableFinalTopLevelLocalWithInitializer = null;
   late final int nonNullableFinalTopLevelLocalWithInitializer = 0;
+  late Never neverLocal;
 }
 
 main() {}
diff --git a/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.strong.expect b/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.strong.expect
index d8369ba..9f76172 100644
--- a/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.strong.expect
+++ b/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.strong.expect
@@ -46,5 +46,10 @@
   final core::int? nonNullableFinalTopLevelLocalWithInitializer;
   function #nonNullableFinalTopLevelLocalWithInitializer#get() → core::int
     return let final core::int? #t14 = nonNullableFinalTopLevelLocalWithInitializer in #t14.==(null) ?{core::int} nonNullableFinalTopLevelLocalWithInitializer = 0 : #t14{core::int};
+  Null neverLocal;
+  function #neverLocal#get() → Never
+    return let final Never? #t15 = neverLocal in #t15.==(null) ?{Never} throw new _in::LateError::localNI("neverLocal") : #t15{Never};
+  function #neverLocal#set(Never #t16) → dynamic
+    return neverLocal = #t16;
 }
 static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.strong.transformed.expect b/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.strong.transformed.expect
index d8369ba..9f76172 100644
--- a/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.strong.transformed.expect
@@ -46,5 +46,10 @@
   final core::int? nonNullableFinalTopLevelLocalWithInitializer;
   function #nonNullableFinalTopLevelLocalWithInitializer#get() → core::int
     return let final core::int? #t14 = nonNullableFinalTopLevelLocalWithInitializer in #t14.==(null) ?{core::int} nonNullableFinalTopLevelLocalWithInitializer = 0 : #t14{core::int};
+  Null neverLocal;
+  function #neverLocal#get() → Never
+    return let final Never? #t15 = neverLocal in #t15.==(null) ?{Never} throw new _in::LateError::localNI("neverLocal") : #t15{Never};
+  function #neverLocal#set(Never #t16) → dynamic
+    return neverLocal = #t16;
 }
 static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.weak.expect b/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.weak.expect
index d8369ba..c2d22e5 100644
--- a/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.weak.expect
+++ b/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.weak.expect
@@ -9,9 +9,9 @@
     return let final core::int? #t1 = nullableTopLevelLocal in _in::isSentinel(#t1) ?{core::int?} throw new _in::LateError::localNI("nullableTopLevelLocal") : #t1{core::int?};
   function #nullableTopLevelLocal#set(core::int? #t2) → dynamic
     return nullableTopLevelLocal = #t2;
-  core::int? nonNullableTopLevelLocal;
+  core::int? nonNullableTopLevelLocal = _in::createSentinel<core::int>();
   function #nonNullableTopLevelLocal#get() → core::int
-    return let final core::int? #t3 = nonNullableTopLevelLocal in #t3.==(null) ?{core::int} throw new _in::LateError::localNI("nonNullableTopLevelLocal") : #t3{core::int};
+    return let final core::int? #t3 = nonNullableTopLevelLocal in _in::isSentinel(#t3) ?{core::int} throw new _in::LateError::localNI("nonNullableTopLevelLocal") : #t3{core::int};
   function #nonNullableTopLevelLocal#set(core::int #t4) → dynamic
     return nonNullableTopLevelLocal = #t4;
   core::int? nullableTopLevelLocalWithInitializer = _in::createSentinel<core::int?>();
@@ -19,9 +19,9 @@
     return let final core::int? #t5 = nullableTopLevelLocalWithInitializer in _in::isSentinel(#t5) ?{core::int?} nullableTopLevelLocalWithInitializer = null : #t5{core::int?};
   function #nullableTopLevelLocalWithInitializer#set(core::int? #t6) → dynamic
     return nullableTopLevelLocalWithInitializer = #t6;
-  core::int? nonNullableTopLevelLocalWithInitializer;
+  core::int? nonNullableTopLevelLocalWithInitializer = _in::createSentinel<core::int>();
   function #nonNullableTopLevelLocalWithInitializer#get() → core::int
-    return let final core::int? #t7 = nonNullableTopLevelLocalWithInitializer in #t7.==(null) ?{core::int} nonNullableTopLevelLocalWithInitializer = 0 : #t7{core::int};
+    return let final core::int? #t7 = nonNullableTopLevelLocalWithInitializer in _in::isSentinel(#t7) ?{core::int} nonNullableTopLevelLocalWithInitializer = 0 : #t7{core::int};
   function #nonNullableTopLevelLocalWithInitializer#set(core::int #t8) → dynamic
     return nonNullableTopLevelLocalWithInitializer = #t8;
   final core::int? nullableFinalTopLevelLocal = _in::createSentinel<core::int?>();
@@ -32,19 +32,24 @@
       return nullableFinalTopLevelLocal = #t10;
     else
       throw new _in::LateError::localAI("nullableFinalTopLevelLocal");
-  final core::int? nonNullableFinalTopLevelLocal;
+  final core::int? nonNullableFinalTopLevelLocal = _in::createSentinel<core::int>();
   function #nonNullableFinalTopLevelLocal#get() → core::int
-    return let final core::int? #t11 = nonNullableFinalTopLevelLocal in #t11.==(null) ?{core::int} throw new _in::LateError::localNI("nonNullableFinalTopLevelLocal") : #t11{core::int};
+    return let final core::int? #t11 = nonNullableFinalTopLevelLocal in _in::isSentinel(#t11) ?{core::int} throw new _in::LateError::localNI("nonNullableFinalTopLevelLocal") : #t11{core::int};
   function #nonNullableFinalTopLevelLocal#set(core::int #t12) → dynamic
-    if(nonNullableFinalTopLevelLocal.==(null))
+    if(_in::isSentinel(nonNullableFinalTopLevelLocal))
       return nonNullableFinalTopLevelLocal = #t12;
     else
       throw new _in::LateError::localAI("nonNullableFinalTopLevelLocal");
   final core::int? nullableFinalTopLevelLocalWithInitializer = _in::createSentinel<core::int?>();
   function #nullableFinalTopLevelLocalWithInitializer#get() → core::int?
     return let final core::int? #t13 = nullableFinalTopLevelLocalWithInitializer in _in::isSentinel(#t13) ?{core::int?} nullableFinalTopLevelLocalWithInitializer = null : #t13{core::int?};
-  final core::int? nonNullableFinalTopLevelLocalWithInitializer;
+  final core::int? nonNullableFinalTopLevelLocalWithInitializer = _in::createSentinel<core::int>();
   function #nonNullableFinalTopLevelLocalWithInitializer#get() → core::int
-    return let final core::int? #t14 = nonNullableFinalTopLevelLocalWithInitializer in #t14.==(null) ?{core::int} nonNullableFinalTopLevelLocalWithInitializer = 0 : #t14{core::int};
+    return let final core::int? #t14 = nonNullableFinalTopLevelLocalWithInitializer in _in::isSentinel(#t14) ?{core::int} nonNullableFinalTopLevelLocalWithInitializer = 0 : #t14{core::int};
+  Null neverLocal = _in::createSentinel<Never>();
+  function #neverLocal#get() → Never
+    return let final Never? #t15 = neverLocal in _in::isSentinel(#t15) ?{Never} throw new _in::LateError::localNI("neverLocal") : #t15{Never};
+  function #neverLocal#set(Never #t16) → dynamic
+    return neverLocal = #t16;
 }
 static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.weak.transformed.expect b/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.weak.transformed.expect
index d8369ba..c2d22e5 100644
--- a/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/late_lowering_sentinel/late_locals.dart.weak.transformed.expect
@@ -9,9 +9,9 @@
     return let final core::int? #t1 = nullableTopLevelLocal in _in::isSentinel(#t1) ?{core::int?} throw new _in::LateError::localNI("nullableTopLevelLocal") : #t1{core::int?};
   function #nullableTopLevelLocal#set(core::int? #t2) → dynamic
     return nullableTopLevelLocal = #t2;
-  core::int? nonNullableTopLevelLocal;
+  core::int? nonNullableTopLevelLocal = _in::createSentinel<core::int>();
   function #nonNullableTopLevelLocal#get() → core::int
-    return let final core::int? #t3 = nonNullableTopLevelLocal in #t3.==(null) ?{core::int} throw new _in::LateError::localNI("nonNullableTopLevelLocal") : #t3{core::int};
+    return let final core::int? #t3 = nonNullableTopLevelLocal in _in::isSentinel(#t3) ?{core::int} throw new _in::LateError::localNI("nonNullableTopLevelLocal") : #t3{core::int};
   function #nonNullableTopLevelLocal#set(core::int #t4) → dynamic
     return nonNullableTopLevelLocal = #t4;
   core::int? nullableTopLevelLocalWithInitializer = _in::createSentinel<core::int?>();
@@ -19,9 +19,9 @@
     return let final core::int? #t5 = nullableTopLevelLocalWithInitializer in _in::isSentinel(#t5) ?{core::int?} nullableTopLevelLocalWithInitializer = null : #t5{core::int?};
   function #nullableTopLevelLocalWithInitializer#set(core::int? #t6) → dynamic
     return nullableTopLevelLocalWithInitializer = #t6;
-  core::int? nonNullableTopLevelLocalWithInitializer;
+  core::int? nonNullableTopLevelLocalWithInitializer = _in::createSentinel<core::int>();
   function #nonNullableTopLevelLocalWithInitializer#get() → core::int
-    return let final core::int? #t7 = nonNullableTopLevelLocalWithInitializer in #t7.==(null) ?{core::int} nonNullableTopLevelLocalWithInitializer = 0 : #t7{core::int};
+    return let final core::int? #t7 = nonNullableTopLevelLocalWithInitializer in _in::isSentinel(#t7) ?{core::int} nonNullableTopLevelLocalWithInitializer = 0 : #t7{core::int};
   function #nonNullableTopLevelLocalWithInitializer#set(core::int #t8) → dynamic
     return nonNullableTopLevelLocalWithInitializer = #t8;
   final core::int? nullableFinalTopLevelLocal = _in::createSentinel<core::int?>();
@@ -32,19 +32,24 @@
       return nullableFinalTopLevelLocal = #t10;
     else
       throw new _in::LateError::localAI("nullableFinalTopLevelLocal");
-  final core::int? nonNullableFinalTopLevelLocal;
+  final core::int? nonNullableFinalTopLevelLocal = _in::createSentinel<core::int>();
   function #nonNullableFinalTopLevelLocal#get() → core::int
-    return let final core::int? #t11 = nonNullableFinalTopLevelLocal in #t11.==(null) ?{core::int} throw new _in::LateError::localNI("nonNullableFinalTopLevelLocal") : #t11{core::int};
+    return let final core::int? #t11 = nonNullableFinalTopLevelLocal in _in::isSentinel(#t11) ?{core::int} throw new _in::LateError::localNI("nonNullableFinalTopLevelLocal") : #t11{core::int};
   function #nonNullableFinalTopLevelLocal#set(core::int #t12) → dynamic
-    if(nonNullableFinalTopLevelLocal.==(null))
+    if(_in::isSentinel(nonNullableFinalTopLevelLocal))
       return nonNullableFinalTopLevelLocal = #t12;
     else
       throw new _in::LateError::localAI("nonNullableFinalTopLevelLocal");
   final core::int? nullableFinalTopLevelLocalWithInitializer = _in::createSentinel<core::int?>();
   function #nullableFinalTopLevelLocalWithInitializer#get() → core::int?
     return let final core::int? #t13 = nullableFinalTopLevelLocalWithInitializer in _in::isSentinel(#t13) ?{core::int?} nullableFinalTopLevelLocalWithInitializer = null : #t13{core::int?};
-  final core::int? nonNullableFinalTopLevelLocalWithInitializer;
+  final core::int? nonNullableFinalTopLevelLocalWithInitializer = _in::createSentinel<core::int>();
   function #nonNullableFinalTopLevelLocalWithInitializer#get() → core::int
-    return let final core::int? #t14 = nonNullableFinalTopLevelLocalWithInitializer in #t14.==(null) ?{core::int} nonNullableFinalTopLevelLocalWithInitializer = 0 : #t14{core::int};
+    return let final core::int? #t14 = nonNullableFinalTopLevelLocalWithInitializer in _in::isSentinel(#t14) ?{core::int} nonNullableFinalTopLevelLocalWithInitializer = 0 : #t14{core::int};
+  Null neverLocal = _in::createSentinel<Never>();
+  function #neverLocal#get() → Never
+    return let final Never? #t15 = neverLocal in _in::isSentinel(#t15) ?{Never} throw new _in::LateError::localNI("neverLocal") : #t15{Never};
+  function #neverLocal#set(Never #t16) → dynamic
+    return neverLocal = #t16;
 }
 static method main() → dynamic {}
diff --git a/runtime/vm/compiler/assembler/assembler_arm.cc b/runtime/vm/compiler/assembler/assembler_arm.cc
index a71c36b..844fd0f 100644
--- a/runtime/vm/compiler/assembler/assembler_arm.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm.cc
@@ -2733,6 +2733,21 @@
   }
 }
 
+void Assembler::LoadFromStack(Register dst, intptr_t depth) {
+  ASSERT(depth >= 0);
+  LoadFromOffset(dst, SPREG, depth * target::kWordSize);
+}
+
+void Assembler::StoreToStack(Register src, intptr_t depth) {
+  ASSERT(depth >= 0);
+  StoreToOffset(src, SPREG, depth * target::kWordSize);
+}
+
+void Assembler::CompareToStack(Register src, intptr_t depth) {
+  LoadFromStack(TMP, depth);
+  CompareRegisters(src, TMP);
+}
+
 void Assembler::StoreToOffset(Register reg,
                               Register base,
                               int32_t offset,
diff --git a/runtime/vm/compiler/assembler/assembler_arm.h b/runtime/vm/compiler/assembler/assembler_arm.h
index 2d6540a..daeb5f7 100644
--- a/runtime/vm/compiler/assembler/assembler_arm.h
+++ b/runtime/vm/compiler/assembler/assembler_arm.h
@@ -942,6 +942,10 @@
     add(reg, base, Operand(index, LSL, scale));
     LoadFromOffset(reg, reg, payload_start - kHeapObjectTag, type);
   }
+  void LoadFromStack(Register dst, intptr_t depth);
+  void StoreToStack(Register src, intptr_t depth);
+  void CompareToStack(Register src, intptr_t depth);
+
   void StoreToOffset(Register reg,
                      Register base,
                      int32_t offset,
@@ -1085,12 +1089,18 @@
     b(is_smi, CC);
   }
 
-  void BranchIfNotSmi(Register reg, Label* label) {
+  // For ARM, the near argument is ignored.
+  void BranchIfNotSmi(Register reg,
+                      Label* label,
+                      JumpDistance distance = kFarJump) {
     tst(reg, Operand(kSmiTagMask));
     b(label, NE);
   }
 
-  void BranchIfSmi(Register reg, Label* label) {
+  // For ARM, the near argument is ignored.
+  void BranchIfSmi(Register reg,
+                   Label* label,
+                   JumpDistance distance = kFarJump) {
     tst(reg, Operand(kSmiTagMask));
     b(label, EQ);
   }
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.cc b/runtime/vm/compiler/assembler/assembler_arm64.cc
index c897811..a5bb185 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm64.cc
@@ -508,6 +508,21 @@
   LoadObjectHelper(dst, object, true);
 }
 
+void Assembler::LoadFromStack(Register dst, intptr_t depth) {
+  ASSERT(depth >= 0);
+  LoadFromOffset(dst, SPREG, depth * target::kWordSize);
+}
+
+void Assembler::StoreToStack(Register src, intptr_t depth) {
+  ASSERT(depth >= 0);
+  StoreToOffset(src, SPREG, depth * target::kWordSize);
+}
+
+void Assembler::CompareToStack(Register src, intptr_t depth) {
+  LoadFromStack(TMP, depth);
+  CompareRegisters(src, TMP);
+}
+
 void Assembler::CompareObject(Register reg, const Object& object) {
   ASSERT(IsOriginalObject(object));
   word offset = 0;
@@ -1176,7 +1191,6 @@
 void Assembler::CompareClassId(Register object,
                                intptr_t class_id,
                                Register scratch) {
-  ASSERT(scratch == kNoRegister);
   LoadClassId(TMP, object);
   CompareImmediate(TMP, class_id);
 }
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.h b/runtime/vm/compiler/assembler/assembler_arm64.h
index 27e0478..2c85181 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.h
+++ b/runtime/vm/compiler/assembler/assembler_arm64.h
@@ -1486,9 +1486,19 @@
     LslImmediate(dst, src, kSmiTagSize);
   }
 
-  void BranchIfNotSmi(Register reg, Label* label) { tbnz(label, reg, kSmiTag); }
+  // For ARM, the near argument is ignored.
+  void BranchIfNotSmi(Register reg,
+                      Label* label,
+                      JumpDistance distance = kFarJump) {
+    tbnz(label, reg, kSmiTag);
+  }
 
-  void BranchIfSmi(Register reg, Label* label) { tbz(label, reg, kSmiTag); }
+  // For ARM, the near argument is ignored.
+  void BranchIfSmi(Register reg,
+                   Label* label,
+                   JumpDistance distance = kFarJump) {
+    tbz(label, reg, kSmiTag);
+  }
 
   void Branch(const Code& code,
               Register pp,
@@ -1579,6 +1589,10 @@
     LoadQFromOffset(dest, base, offset - kHeapObjectTag);
   }
 
+  void LoadFromStack(Register dst, intptr_t depth);
+  void StoreToStack(Register src, intptr_t depth);
+  void CompareToStack(Register src, intptr_t depth);
+
   void StoreToOffset(Register src,
                      Register base,
                      int32_t offset,
diff --git a/runtime/vm/compiler/assembler/assembler_ia32.cc b/runtime/vm/compiler/assembler/assembler_ia32.cc
index facc34e..521f53f 100644
--- a/runtime/vm/compiler/assembler/assembler_ia32.cc
+++ b/runtime/vm/compiler/assembler/assembler_ia32.cc
@@ -1762,6 +1762,39 @@
   cmpl(a, b);
 }
 
+void Assembler::LoadFromOffset(Register reg,
+                               Register base,
+                               int32_t offset,
+                               OperandSize type) {
+  switch (type) {
+    case kByte:
+      return movsxb(reg, Address(base, offset));
+    case kUnsignedByte:
+      return movzxb(reg, Address(base, offset));
+    case kTwoBytes:
+      return movsxw(reg, Address(base, offset));
+    case kUnsignedTwoBytes:
+      return movzxw(reg, Address(base, offset));
+    case kFourBytes:
+      return movl(reg, Address(base, offset));
+    default:
+      UNREACHABLE();
+      break;
+  }
+}
+
+void Assembler::LoadFromStack(Register dst, intptr_t depth) {
+  movl(dst, Address(ESP, depth * target::kWordSize));
+}
+
+void Assembler::StoreToStack(Register src, intptr_t depth) {
+  movl(Address(ESP, depth * target::kWordSize), src);
+}
+
+void Assembler::CompareToStack(Register src, intptr_t depth) {
+  cmpl(src, Address(ESP, depth * target::kWordSize));
+}
+
 void Assembler::MoveRegister(Register to, Register from) {
   if (to != from) {
     movl(to, from);
diff --git a/runtime/vm/compiler/assembler/assembler_ia32.h b/runtime/vm/compiler/assembler/assembler_ia32.h
index a927fec..0c6ed99 100644
--- a/runtime/vm/compiler/assembler/assembler_ia32.h
+++ b/runtime/vm/compiler/assembler/assembler_ia32.h
@@ -572,9 +572,33 @@
 
   void Ret() { ret(); }
   void CompareRegisters(Register a, Register b);
-  void BranchIf(Condition condition, Label* label) { j(condition, label); }
+  void BranchIf(Condition condition,
+                Label* label,
+                JumpDistance distance = kFarJump) {
+    j(condition, label, distance);
+  }
 
+  void LoadFromOffset(Register reg,
+                      Register base,
+                      int32_t offset,
+                      OperandSize type = kFourBytes);
   void LoadField(Register dst, FieldAddress address) { movl(dst, address); }
+  void LoadFieldFromOffset(Register reg,
+                           Register base,
+                           int32_t offset,
+                           OperandSize type = kFourBytes) {
+    LoadFromOffset(reg, base, offset - kHeapObjectTag, type);
+  }
+  void LoadIndexedFieldFromOffset(Register reg,
+                                  Register base,
+                                  int32_t offset,
+                                  Register index,
+                                  ScaleFactor scale) {
+    LoadField(reg, FieldAddress(base, index, scale, offset));
+  }
+  void LoadFromStack(Register dst, intptr_t depth);
+  void StoreToStack(Register src, intptr_t depth);
+  void CompareToStack(Register src, intptr_t depth);
   void LoadMemoryValue(Register dst, Register base, int32_t offset) {
     movl(dst, Address(base, offset));
   }
@@ -792,19 +816,25 @@
 
   void SmiUntag(Register reg) { sarl(reg, Immediate(kSmiTagSize)); }
 
-  void BranchIfNotSmi(Register reg, Label* label) {
+  void BranchIfNotSmi(Register reg,
+                      Label* label,
+                      JumpDistance distance = kFarJump) {
     testl(reg, Immediate(kSmiTagMask));
-    j(NOT_ZERO, label);
+    j(NOT_ZERO, label, distance);
   }
 
-  void BranchIfSmi(Register reg, Label* label) {
+  void BranchIfSmi(Register reg,
+                   Label* label,
+                   JumpDistance distance = kFarJump) {
     testl(reg, Immediate(kSmiTagMask));
-    j(ZERO, label);
+    j(ZERO, label, distance);
   }
 
   void Align(intptr_t alignment, intptr_t offset);
   void Bind(Label* label);
-  void Jump(Label* label) { jmp(label); }
+  void Jump(Label* label, JumpDistance distance = kFarJump) {
+    jmp(label, distance);
+  }
 
   // Moves one word from the memory at [from] to the memory at [to].
   // Needs a temporary register.
diff --git a/runtime/vm/compiler/assembler/assembler_x64.cc b/runtime/vm/compiler/assembler/assembler_x64.cc
index f33e605..e15554b 100644
--- a/runtime/vm/compiler/assembler/assembler_x64.cc
+++ b/runtime/vm/compiler/assembler/assembler_x64.cc
@@ -1057,6 +1057,20 @@
   cmpq(a, b);
 }
 
+void Assembler::LoadFromStack(Register dst, intptr_t depth) {
+  ASSERT(depth >= 0);
+  movq(dst, Address(SPREG, depth * target::kWordSize));
+}
+
+void Assembler::StoreToStack(Register src, intptr_t depth) {
+  ASSERT(depth >= 0);
+  movq(Address(SPREG, depth * target::kWordSize), src);
+}
+
+void Assembler::CompareToStack(Register src, intptr_t depth) {
+  cmpq(Address(SPREG, depth * target::kWordSize), src);
+}
+
 void Assembler::MoveRegister(Register to, Register from) {
   if (to != from) {
     movq(to, from);
@@ -2200,7 +2214,6 @@
 void Assembler::CompareClassId(Register object,
                                intptr_t class_id,
                                Register scratch) {
-  ASSERT(scratch == kNoRegister);
   LoadClassId(TMP, object);
   cmpl(TMP, Immediate(class_id));
 }
diff --git a/runtime/vm/compiler/assembler/assembler_x64.h b/runtime/vm/compiler/assembler/assembler_x64.h
index 0634382..01ed17b 100644
--- a/runtime/vm/compiler/assembler/assembler_x64.h
+++ b/runtime/vm/compiler/assembler/assembler_x64.h
@@ -857,14 +857,18 @@
 
   void SmiUntag(Register reg) { sarq(reg, Immediate(kSmiTagSize)); }
 
-  void BranchIfNotSmi(Register reg, Label* label) {
+  void BranchIfNotSmi(Register reg,
+                      Label* label,
+                      JumpDistance distance = kFarJump) {
     testq(reg, Immediate(kSmiTagMask));
-    j(NOT_ZERO, label);
+    j(NOT_ZERO, label, distance);
   }
 
-  void BranchIfSmi(Register reg, Label* label) {
+  void BranchIfSmi(Register reg,
+                   Label* label,
+                   JumpDistance distance = kFarJump) {
     testq(reg, Immediate(kSmiTagMask));
-    j(ZERO, label);
+    j(ZERO, label, distance);
   }
 
   void Align(int alignment, intptr_t offset);
@@ -905,6 +909,9 @@
                           OperandSize sz = kEightBytes) {
     LoadFromOffset(dst, FieldAddress(base, index, scale, payload_offset), sz);
   }
+  void LoadFromStack(Register dst, intptr_t depth);
+  void StoreToStack(Register src, intptr_t depth);
+  void CompareToStack(Register src, intptr_t depth);
   void LoadMemoryValue(Register dst, Register base, int32_t offset) {
     movq(dst, Address(base, offset));
   }
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc
index 34df72b..4902136 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc
@@ -2300,28 +2300,417 @@
   return true;
 }
 
+// Generates function type check.
+//
+// See [GenerateInlineInstanceof] for calling convention.
+SubtypeTestCachePtr FlowGraphCompiler::GenerateFunctionTypeTest(
+    TokenPosition token_pos,
+    const AbstractType& type,
+    compiler::Label* is_instance_lbl,
+    compiler::Label* is_not_instance_lbl) {
+  __ Comment("FunctionTypeTest");
+
+  __ BranchIfSmi(TypeTestABI::kInstanceReg, is_not_instance_lbl);
+  // Load the type into the right register for the subtype test cache check.
+  __ LoadUniqueObject(TypeTestABI::kDstTypeReg, type);
+  // Uninstantiated type class is known at compile time, but the type
+  // arguments are determined at runtime by the instantiator(s).
+  return GenerateCallSubtypeTestStub(kTestTypeSevenArgs, is_instance_lbl,
+                                     is_not_instance_lbl);
+}
+
+// Inputs (from TypeTestABI):
+//   - kInstanceReg : instance to test against.
+//   - kInstantiatorTypeArgumentsReg : instantiator type arguments (if needed).
+//   - kFunctionTypeArgumentsReg : function type arguments (if needed).
+//
+// Preserves all input registers.
+//
+// Clobbers kDstTypeReg, kSubtypeTestCacheReg and kSubtypeTestCacheResultReg at
+// a minimum, may clobber additional registers depending on architecture. See
+// GenerateSubtypeNTestCacheStub for architecture-specific registers that should
+// be saved across a subtype test cache stub call.
+//
+// Note that this inlined code must be followed by the runtime_call code, as it
+// may fall through to it. Otherwise, this inline code will jump to the label
+// is_instance or to the label is_not_instance.
+SubtypeTestCachePtr FlowGraphCompiler::GenerateInlineInstanceof(
+    TokenPosition token_pos,
+    const AbstractType& type,
+    compiler::Label* is_instance_lbl,
+    compiler::Label* is_not_instance_lbl) {
+  __ Comment("InlineInstanceof");
+
+  if (type.IsFunctionType()) {
+    return GenerateFunctionTypeTest(token_pos, type, is_instance_lbl,
+                                    is_not_instance_lbl);
+  }
+
+  if (type.IsInstantiated()) {
+    const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
+    // A class equality check is only applicable with a dst type (not a
+    // function type) of a non-parameterized class or with a raw dst type of
+    // a parameterized class.
+    if (type_class.NumTypeArguments() > 0) {
+      return GenerateInstantiatedTypeWithArgumentsTest(
+          token_pos, type, is_instance_lbl, is_not_instance_lbl);
+      // Fall through to runtime call.
+    }
+    const bool has_fall_through = GenerateInstantiatedTypeNoArgumentsTest(
+        token_pos, type, is_instance_lbl, is_not_instance_lbl);
+    if (has_fall_through) {
+      // If test non-conclusive so far, try the inlined type-test cache.
+      // 'type' is known at compile time.
+      return GenerateSubtype1TestCacheLookup(
+          token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
+    } else {
+      return SubtypeTestCache::null();
+    }
+  }
+  return GenerateUninstantiatedTypeTest(token_pos, type, is_instance_lbl,
+                                        is_not_instance_lbl);
+}
+
 FlowGraphCompiler::TypeTestStubKind
 FlowGraphCompiler::GetTypeTestStubKindForTypeParameter(
     const TypeParameter& type_param) {
   // If it's guaranteed, by type-parameter bound, that the type parameter will
-  // never have a value of a function type, then we can safely do a 4-type
-  // test instead of a 6-type test.
+  // never have a value of a function type, then we can safely do a 5-type
+  // test instead of a 7-type test.
   AbstractType& bound = AbstractType::Handle(zone(), type_param.bound());
   bound = bound.UnwrapFutureOr();
   return !bound.IsTopTypeForSubtyping() && !bound.IsObjectType() &&
                  !bound.IsFunctionType() && !bound.IsDartFunctionType() &&
                  bound.IsType()
-             ? kTestTypeFourArgs
-             : kTestTypeSixArgs;
+             ? kTestTypeFiveArgs
+             : kTestTypeSevenArgs;
+}
+
+// Generates quick and subtype cache tests when only the instance need be
+// checked. Jumps to 'is_instance' or 'is_not_instance' respectively, if any
+// generated check is conclusive, otherwise falls through if further checking is
+// required.
+//
+// See [GenerateInlineInstanceof] for calling convention.
+SubtypeTestCachePtr FlowGraphCompiler::GenerateSubtype1TestCacheLookup(
+    TokenPosition token_pos,
+    const Class& type_class,
+    compiler::Label* is_instance_lbl,
+    compiler::Label* is_not_instance_lbl) {
+  __ Comment("Subtype1TestCacheLookup");
+#if defined(DEBUG)
+  compiler::Label ok;
+  __ BranchIfNotSmi(TypeTestABI::kInstanceReg, &ok);
+  __ Breakpoint();
+  __ Bind(&ok);
+#endif
+  // Check immediate superclass equality. If type_class is Object, then testing
+  // supertype may yield a wrong result for Null in NNBD strong mode (because
+  // Null also extends Object).
+  if (!type_class.IsObjectClass() || !Isolate::Current()->null_safety()) {
+    // We don't use TypeTestABI::kScratchReg for the first scratch register as
+    // it is not defined on IA32. Instead, we use the subtype test cache
+    // register, as it is clobbered by the subtype test cache stub call anyway.
+    const Register kScratch1Reg = TypeTestABI::kSubtypeTestCacheReg;
+#if defined(TARGET_ARCH_IA32)
+    // We don't use TypeTestABI::kScratchReg as it is not defined on IA32.
+    // Instead, we pick another TypeTestABI register and push/pop it around
+    // the uses of the second scratch register.
+    const Register kScratch2Reg = TypeTestABI::kDstTypeReg;
+    __ PushRegister(kScratch2Reg);
+#else
+    // We can use TypeTestABI::kScratchReg for the second scratch register, as
+    // IA32 is handled separately.
+    const Register kScratch2Reg = TypeTestABI::kScratchReg;
+#endif
+    static_assert(kScratch1Reg != kScratch2Reg,
+                  "Scratch registers must be distinct");
+    __ LoadClassId(kScratch2Reg, TypeTestABI::kInstanceReg);
+    __ LoadClassById(kScratch1Reg, kScratch2Reg);
+#if defined(TARGET_ARCH_IA32)
+    // kScratch2 is no longer used, so restore it.
+    __ PopRegister(kScratch2Reg);
+#endif
+    __ LoadFieldFromOffset(kScratch1Reg, kScratch1Reg,
+                           compiler::target::Class::super_type_offset());
+    __ LoadFieldFromOffset(kScratch1Reg, kScratch1Reg,
+                           compiler::target::Type::type_class_id_offset());
+    __ CompareImmediate(kScratch1Reg, Smi::RawValue(type_class.id()));
+    __ BranchIf(EQUAL, is_instance_lbl);
+  }
+
+  return GenerateCallSubtypeTestStub(kTestTypeOneArg, is_instance_lbl,
+                                     is_not_instance_lbl);
+}
+
+// Generates quick and subtype cache tests for an instantiated generic type.
+// Jumps to 'is_instance' or 'is_not_instance' respectively, if any generated
+// check is conclusive, otherwise falls through if further checking is required.
+//
+// See [GenerateInlineInstanceof] for calling convention.
+SubtypeTestCachePtr
+FlowGraphCompiler::GenerateInstantiatedTypeWithArgumentsTest(
+    TokenPosition token_pos,
+    const AbstractType& type,
+    compiler::Label* is_instance_lbl,
+    compiler::Label* is_not_instance_lbl) {
+  __ Comment("InstantiatedTypeWithArgumentsTest");
+  ASSERT(type.IsInstantiated());
+  ASSERT(!type.IsFunctionType());
+  const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
+  ASSERT(type_class.NumTypeArguments() > 0);
+  const Type& smi_type = Type::Handle(zone(), Type::SmiType());
+  const bool smi_is_ok = smi_type.IsSubtypeOf(type, Heap::kOld);
+  __ BranchIfSmi(TypeTestABI::kInstanceReg,
+                 smi_is_ok ? is_instance_lbl : is_not_instance_lbl);
+
+  const intptr_t num_type_args = type_class.NumTypeArguments();
+  const intptr_t num_type_params = type_class.NumTypeParameters();
+  const intptr_t from_index = num_type_args - num_type_params;
+  const TypeArguments& type_arguments =
+      TypeArguments::ZoneHandle(zone(), type.arguments());
+  const bool is_raw_type = type_arguments.IsNull() ||
+                           type_arguments.IsRaw(from_index, num_type_params);
+  // We don't use TypeTestABI::kScratchReg as it is not defined on IA32.
+  // Instead, we use the subtype test cache register, as it is clobbered by the
+  // subtype test cache stub call anyway.
+  const Register kScratchReg = TypeTestABI::kSubtypeTestCacheReg;
+  if (is_raw_type) {
+    // dynamic type argument, check only classes.
+    __ LoadClassId(kScratchReg, TypeTestABI::kInstanceReg);
+    __ CompareImmediate(kScratchReg, type_class.id());
+    __ BranchIf(EQUAL, is_instance_lbl);
+    // List is a very common case.
+    if (IsListClass(type_class)) {
+      GenerateListTypeCheck(kScratchReg, is_instance_lbl);
+    }
+    return GenerateSubtype1TestCacheLookup(
+        token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
+  }
+  // If one type argument only, check if type argument is a top type.
+  if (type_arguments.Length() == 1) {
+    const AbstractType& tp_argument =
+        AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
+    if (tp_argument.IsTopTypeForSubtyping()) {
+      // Instance class test only necessary.
+      return GenerateSubtype1TestCacheLookup(
+          token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
+    }
+  }
+
+  // Load the type into the right register for the subtype test cache check.
+  __ LoadUniqueObject(TypeTestABI::kDstTypeReg, type);
+  // Regular subtype test cache involving instance's type arguments.
+  return GenerateCallSubtypeTestStub(kTestTypeThreeArgs, is_instance_lbl,
+                                     is_not_instance_lbl);
+}
+
+// Generates quick and subtype cache tests for an instantiated non-generic type.
+// Jumps to 'is_instance' or 'is_not_instance' respectively, if any generated
+// check is conclusive. Returns whether the code will fall through for further
+// type checking because the checks are not exhaustive.
+//
+// See [GenerateInlineInstanceof] for calling convention.
+//
+// Uses kScratchReg, so this implementation cannot be shared with IA32.
+bool FlowGraphCompiler::GenerateInstantiatedTypeNoArgumentsTest(
+    TokenPosition token_pos,
+    const AbstractType& type,
+    compiler::Label* is_instance_lbl,
+    compiler::Label* is_not_instance_lbl) {
+  __ Comment("InstantiatedTypeNoArgumentsTest");
+  ASSERT(type.IsInstantiated());
+  ASSERT(!type.IsFunctionType());
+  const Class& type_class = Class::Handle(zone(), type.type_class());
+  ASSERT(type_class.NumTypeArguments() == 0);
+
+  // We don't use TypeTestABI::kScratchReg as it is not defined on IA32.
+  // Instead, we use the subtype test cache register, as it is clobbered by the
+  // subtype test cache stub call anyway.
+  const Register kScratchReg = TypeTestABI::kSubtypeTestCacheReg;
+
+  const Class& smi_class = Class::Handle(zone(), Smi::Class());
+  const bool smi_is_ok =
+      Class::IsSubtypeOf(smi_class, Object::null_type_arguments(),
+                         Nullability::kNonNullable, type, Heap::kOld);
+  __ BranchIfSmi(TypeTestABI::kInstanceReg,
+                 smi_is_ok ? is_instance_lbl : is_not_instance_lbl);
+  __ LoadClassId(kScratchReg, TypeTestABI::kInstanceReg);
+  // Bool interface can be implemented only by core class Bool.
+  if (type.IsBoolType()) {
+    __ CompareImmediate(kScratchReg, kBoolCid);
+    __ BranchIf(EQUAL, is_instance_lbl);
+    __ Jump(is_not_instance_lbl);
+    return false;
+  }
+  // Custom checking for numbers (Smi, Mint and Double).
+  // Note that instance is not Smi (checked above).
+  if (type.IsNumberType() || type.IsIntType() || type.IsDoubleType()) {
+    GenerateNumberTypeCheck(kScratchReg, type, is_instance_lbl,
+                            is_not_instance_lbl);
+    return false;
+  }
+  if (type.IsStringType()) {
+    GenerateStringTypeCheck(kScratchReg, is_instance_lbl, is_not_instance_lbl);
+    return false;
+  }
+  if (type.IsDartFunctionType()) {
+    // Check if instance is a closure.
+    __ CompareImmediate(kScratchReg, kClosureCid);
+    __ BranchIf(EQUAL, is_instance_lbl);
+    return true;
+  }
+
+  // Fast case for cid-range based checks.
+  // Warning: This code destroys the contents of [kScratchReg], so this should
+  // be the last check in this method. It returns whether the checks were
+  // exhaustive, so we negate it to indicate whether we'll fall through.
+  return !GenerateSubtypeRangeCheck(kScratchReg, type_class, is_instance_lbl);
+}
+
+// Generates inlined check if 'type' is a type parameter or type itself.
+//
+// See [GenerateInlineInstanceof] for calling convention.
+SubtypeTestCachePtr FlowGraphCompiler::GenerateUninstantiatedTypeTest(
+    TokenPosition token_pos,
+    const AbstractType& type,
+    compiler::Label* is_instance_lbl,
+    compiler::Label* is_not_instance_lbl) {
+  __ Comment("UninstantiatedTypeTest");
+  ASSERT(!type.IsInstantiated());
+  ASSERT(!type.IsFunctionType());
+  // Skip check if destination is a dynamic type.
+  if (type.IsTypeParameter()) {
+    // We don't use TypeTestABI::kScratchReg as it is not defined on IA32.
+    // Instead, we use the subtype test cache register, as it is clobbered by
+    // the subtype test cache stub call anyway.
+    const Register kScratchReg = TypeTestABI::kSubtypeTestCacheReg;
+
+    const TypeParameter& type_param = TypeParameter::Cast(type);
+
+    const Register kTypeArgumentsReg =
+        type_param.IsClassTypeParameter()
+            ? TypeTestABI::kInstantiatorTypeArgumentsReg
+            : TypeTestABI::kFunctionTypeArgumentsReg;
+    // Check if type arguments are null, i.e. equivalent to vector of dynamic.
+    __ CompareObject(kTypeArgumentsReg, Object::null_object());
+    __ BranchIf(EQUAL, is_instance_lbl);
+    __ LoadFieldFromOffset(kScratchReg, kTypeArgumentsReg,
+                           TypeArguments::type_at_offset(type_param.index()));
+    // kScratchReg: Concrete type of type.
+    // Check if type argument is dynamic, Object?, or void.
+    __ CompareObject(kScratchReg, Object::dynamic_type());
+    __ BranchIf(EQUAL, is_instance_lbl);
+    __ CompareObject(
+        kScratchReg,
+        Type::ZoneHandle(zone(),
+                         isolate()->object_store()->nullable_object_type()));
+    __ BranchIf(EQUAL, is_instance_lbl);
+    __ CompareObject(kScratchReg, Object::void_type());
+    __ BranchIf(EQUAL, is_instance_lbl);
+
+    // For Smi check quickly against int and num interfaces.
+    compiler::Label not_smi;
+    __ BranchIfNotSmi(TypeTestABI::kInstanceReg, &not_smi,
+                      compiler::Assembler::kNearJump);
+    __ CompareObject(kScratchReg, Type::ZoneHandle(zone(), Type::IntType()));
+    __ BranchIf(EQUAL, is_instance_lbl);
+    __ CompareObject(kScratchReg, Type::ZoneHandle(zone(), Type::Number()));
+    __ BranchIf(EQUAL, is_instance_lbl);
+    // Smi can be handled by type test cache.
+    __ Bind(&not_smi);
+
+    // Load the type into the right register for the subtype test cache check.
+    __ LoadUniqueObject(TypeTestABI::kDstTypeReg, type);
+    const auto test_kind = GetTypeTestStubKindForTypeParameter(type_param);
+    return GenerateCallSubtypeTestStub(test_kind, is_instance_lbl,
+                                       is_not_instance_lbl);
+  }
+  if (type.IsType()) {
+    // The only uninstantiated type to which a Smi is assignable is FutureOr<T>,
+    // as T might be a top type or int or num when instantiated
+    if (!type.IsFutureOrType()) {
+      __ BranchIfSmi(TypeTestABI::kInstanceReg, is_not_instance_lbl);
+    }
+    // Load the type into the right register for the subtype test cache check.
+    __ LoadUniqueObject(TypeTestABI::kDstTypeReg, type);
+    // Uninstantiated type class is known at compile time, but the type
+    // arguments are determined at runtime by the instantiator(s).
+    return GenerateCallSubtypeTestStub(kTestTypeFiveArgs, is_instance_lbl,
+                                       is_not_instance_lbl);
+  }
+  return SubtypeTestCache::null();
 }
 
 #if !defined(TARGET_ARCH_IA32)
+// If instanceof type test cannot be performed successfully at compile time and
+// therefore eliminated, optimize it by adding inlined tests for:
+// - Null -> see comment below.
+// - Smi -> compile time subtype check (only if dst class is not parameterized).
+// - Class equality (only if class is not parameterized).
+// Inputs (from TypeTestABI):
+// - kInstanceReg: object.
+// - kInstantiatorTypeArgumentsReg: instantiator type arguments or raw_null.
+// - kFunctionTypeArgumentsReg: function type arguments or raw_null.
+// Returns:
+// - true or false in kInstanceOfResultReg.
+void FlowGraphCompiler::GenerateInstanceOf(TokenPosition token_pos,
+                                           intptr_t deopt_id,
+                                           const AbstractType& type,
+                                           LocationSummary* locs) {
+  ASSERT(type.IsFinalized());
+  ASSERT(!type.IsTopTypeForInstanceOf());  // Already checked.
+
+  compiler::Label is_instance, is_not_instance;
+  // 'null' is an instance of Null, Object*, Never*, void, and dynamic.
+  // In addition, 'null' is an instance of any nullable type.
+  // It is also an instance of FutureOr<T> if it is an instance of T.
+  const AbstractType& unwrapped_type =
+      AbstractType::Handle(type.UnwrapFutureOr());
+  if (!unwrapped_type.IsTypeParameter() || unwrapped_type.IsNullable()) {
+    // Only nullable type parameter remains nullable after instantiation.
+    // See NullIsInstanceOf().
+    __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
+    __ BranchIf(EQUAL,
+                (unwrapped_type.IsNullable() ||
+                 (unwrapped_type.IsLegacy() && unwrapped_type.IsNeverType()))
+                    ? &is_instance
+                    : &is_not_instance);
+  }
+
+  // Generate inline instanceof test.
+  SubtypeTestCache& test_cache = SubtypeTestCache::ZoneHandle(zone());
+  // kInstanceReg, kInstantiatorTypeArgumentsReg, and kFunctionTypeArgumentsReg
+  // are preserved across the call.
+  test_cache =
+      GenerateInlineInstanceof(token_pos, type, &is_instance, &is_not_instance);
+
+  // test_cache is null if there is no fall-through.
+  compiler::Label done;
+  if (!test_cache.IsNull()) {
+    // Generate Runtime call.
+    __ LoadUniqueObject(TypeTestABI::kDstTypeReg, type);
+    __ LoadUniqueObject(TypeTestABI::kSubtypeTestCacheReg, test_cache);
+    GenerateStubCall(token_pos, StubCode::InstanceOf(),
+                     /*kind=*/PcDescriptorsLayout::kOther, locs, deopt_id);
+    __ Jump(&done, compiler::Assembler::kNearJump);
+  }
+  __ Bind(&is_not_instance);
+  __ LoadObject(TypeTestABI::kInstanceOfResultReg, Bool::Get(false));
+  __ Jump(&done, compiler::Assembler::kNearJump);
+
+  __ Bind(&is_instance);
+  __ LoadObject(TypeTestABI::kInstanceOfResultReg, Bool::Get(true));
+  __ Bind(&done);
+}
+
 // Expected inputs (from TypeTestABI):
 // - kInstanceReg: instance (preserved).
+// - kDstTypeReg: destination type (for test_kind != kTestTypeOneArg).
 // - kInstantiatorTypeArgumentsReg: instantiator type arguments
-//   (for test_kind == kTestTypeFourArg or test_kind == kTestTypeSixArg).
+//   (for test_kind == kTestTypeFiveArg or test_kind == kTestTypeSevenArg).
 // - kFunctionTypeArgumentsReg: function type arguments
-//   (for test_kind == kTestTypeFourArg or test_kind == kTestTypeSixArg).
+//   (for test_kind == kTestTypeFiveArg or test_kind == kTestTypeSevenArg).
 //
 // See the arch-specific GenerateSubtypeNTestCacheStub method to see which
 // registers may need saving across this call.
@@ -2334,12 +2723,12 @@
   __ LoadUniqueObject(TypeTestABI::kSubtypeTestCacheReg, type_test_cache);
   if (test_kind == kTestTypeOneArg) {
     __ Call(StubCode::Subtype1TestCache());
-  } else if (test_kind == kTestTypeTwoArgs) {
-    __ Call(StubCode::Subtype2TestCache());
-  } else if (test_kind == kTestTypeFourArgs) {
-    __ Call(StubCode::Subtype4TestCache());
-  } else if (test_kind == kTestTypeSixArgs) {
-    __ Call(StubCode::Subtype6TestCache());
+  } else if (test_kind == kTestTypeThreeArgs) {
+    __ Call(StubCode::Subtype3TestCache());
+  } else if (test_kind == kTestTypeFiveArgs) {
+    __ Call(StubCode::Subtype5TestCache());
+  } else if (test_kind == kTestTypeSevenArgs) {
+    __ Call(StubCode::Subtype7TestCache());
   } else {
     UNREACHABLE();
   }
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.h b/runtime/vm/compiler/backend/flow_graph_compiler.h
index 31d849d..729c639 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.h
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.h
@@ -1076,9 +1076,9 @@
 
   enum TypeTestStubKind {
     kTestTypeOneArg,
-    kTestTypeTwoArgs,
-    kTestTypeFourArgs,
-    kTestTypeSixArgs,
+    kTestTypeThreeArgs,
+    kTestTypeFiveArgs,
+    kTestTypeSevenArgs,
   };
 
   // Returns type test stub kind for a type test against type parameter type.
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc b/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
index 759a411..1d85187 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
@@ -224,389 +224,6 @@
   __ Bind(&fall_through);
 }
 
-// Jumps to labels 'is_instance' or 'is_not_instance' respectively, if
-// type test is conclusive, otherwise fallthrough if a type test could not
-// be completed.
-// R0: instance being type checked (preserved).
-// Clobbers R1, R2.
-SubtypeTestCachePtr
-FlowGraphCompiler::GenerateInstantiatedTypeWithArgumentsTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("InstantiatedTypeWithArgumentsTest");
-  ASSERT(type.IsInstantiated());
-  ASSERT(!type.IsFunctionType());
-  const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
-  ASSERT(type_class.NumTypeArguments() > 0);
-  const Type& smi_type = Type::Handle(zone(), Type::SmiType());
-  const bool smi_is_ok = smi_type.IsSubtypeOf(type, Heap::kOld);
-  __ tst(TypeTestABI::kInstanceReg, compiler::Operand(kSmiTagMask));
-  if (smi_is_ok) {
-    // Fast case for type = FutureOr<int/num/top-type>.
-    __ b(is_instance_lbl, EQ);
-  } else {
-    __ b(is_not_instance_lbl, EQ);
-  }
-  const intptr_t num_type_args = type_class.NumTypeArguments();
-  const intptr_t num_type_params = type_class.NumTypeParameters();
-  const intptr_t from_index = num_type_args - num_type_params;
-  const TypeArguments& type_arguments =
-      TypeArguments::ZoneHandle(zone(), type.arguments());
-  const bool is_raw_type = type_arguments.IsNull() ||
-                           type_arguments.IsRaw(from_index, num_type_params);
-  if (is_raw_type) {
-    const Register kClassIdReg = R2;
-    // dynamic type argument, check only classes.
-    __ LoadClassId(kClassIdReg, TypeTestABI::kInstanceReg);
-    __ CompareImmediate(kClassIdReg, type_class.id());
-    __ b(is_instance_lbl, EQ);
-    // List is a very common case.
-    if (IsListClass(type_class)) {
-      GenerateListTypeCheck(kClassIdReg, is_instance_lbl);
-    }
-    return GenerateSubtype1TestCacheLookup(
-        token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-  }
-  // If one type argument only, check if type argument is a top type.
-  if (type_arguments.Length() == 1) {
-    const AbstractType& tp_argument =
-        AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
-    if (tp_argument.IsTopTypeForSubtyping()) {
-      // Instance class test only necessary.
-      return GenerateSubtype1TestCacheLookup(
-          token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-    }
-  }
-
-  // Regular subtype test cache involving instance's type arguments.
-  return GenerateCallSubtypeTestStub(kTestTypeTwoArgs, is_instance_lbl,
-                                     is_not_instance_lbl);
-}
-
-// Testing against an instantiated type with no arguments, without
-// SubtypeTestCache.
-// R0: instance being type checked (preserved).
-// Clobbers R2, R3.
-// Returns true if there is a fallthrough.
-bool FlowGraphCompiler::GenerateInstantiatedTypeNoArgumentsTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("InstantiatedTypeNoArgumentsTest");
-  ASSERT(type.IsInstantiated());
-  ASSERT(!type.IsFunctionType());
-  const Class& type_class = Class::Handle(zone(), type.type_class());
-  ASSERT(type_class.NumTypeArguments() == 0);
-
-  __ tst(TypeTestABI::kInstanceReg, compiler::Operand(kSmiTagMask));
-  // If instance is Smi, check directly.
-  const Class& smi_class = Class::Handle(zone(), Smi::Class());
-  if (Class::IsSubtypeOf(smi_class, Object::null_type_arguments(),
-                         Nullability::kNonNullable, type, Heap::kOld)) {
-    // Fast case for type = int/num/top-type.
-    __ b(is_instance_lbl, EQ);
-  } else {
-    __ b(is_not_instance_lbl, EQ);
-  }
-  const Register kClassIdReg = R2;
-  __ LoadClassId(kClassIdReg, TypeTestABI::kInstanceReg);
-  // Bool interface can be implemented only by core class Bool.
-  if (type.IsBoolType()) {
-    __ CompareImmediate(kClassIdReg, kBoolCid);
-    __ b(is_instance_lbl, EQ);
-    __ b(is_not_instance_lbl);
-    return false;
-  }
-  // Custom checking for numbers (Smi, Mint and Double).
-  // Note that instance is not Smi (checked above).
-  if (type.IsNumberType() || type.IsIntType() || type.IsDoubleType()) {
-    GenerateNumberTypeCheck(kClassIdReg, type, is_instance_lbl,
-                            is_not_instance_lbl);
-    return false;
-  }
-  if (type.IsStringType()) {
-    GenerateStringTypeCheck(kClassIdReg, is_instance_lbl, is_not_instance_lbl);
-    return false;
-  }
-  if (type.IsDartFunctionType()) {
-    // Check if instance is a closure.
-    __ CompareImmediate(kClassIdReg, kClosureCid);
-    __ b(is_instance_lbl, EQ);
-    return true;  // Fall through
-  }
-
-  // Fast case for cid-range based checks.
-  // Warning: This code destroys the contents of [kClassIdReg].
-  if (GenerateSubtypeRangeCheck(kClassIdReg, type_class, is_instance_lbl)) {
-    return false;
-  }
-
-  // Otherwise fallthrough, result non-conclusive.
-  return true;
-}
-
-// Uses SubtypeTestCache to store instance class and result.
-// R0: instance to test.
-// Clobbers R1-R4, R8, R9.
-// Immediate class test already done.
-// TODO(srdjan): Implement a quicker subtype check, as type test
-// arrays can grow too high, but they may be useful when optimizing
-// code (type-feedback).
-SubtypeTestCachePtr FlowGraphCompiler::GenerateSubtype1TestCacheLookup(
-    TokenPosition token_pos,
-    const Class& type_class,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("Subtype1TestCacheLookup");
-#if defined(DEBUG)
-  compiler::Label ok;
-  __ BranchIfNotSmi(TypeTestABI::kInstanceReg, &ok);
-  __ Breakpoint();
-  __ Bind(&ok);
-#endif
-  __ LoadClassId(R2, TypeTestABI::kInstanceReg);
-  __ LoadClassById(R1, R2);
-  // R1: instance class.
-  // Check immediate superclass equality. If type_class is Object, then testing
-  // supertype may yield a wrong result for Null in NNBD strong mode (because
-  // Null also extends Object).
-  if (!type_class.IsObjectClass() || !Isolate::Current()->null_safety()) {
-    __ ldr(R2, compiler::FieldAddress(
-                   R1, compiler::target::Class::super_type_offset()));
-    __ ldr(R2, compiler::FieldAddress(
-                   R2, compiler::target::Type::type_class_id_offset()));
-    __ CompareImmediate(R2, Smi::RawValue(type_class.id()));
-    __ b(is_instance_lbl, EQ);
-  }
-
-  return GenerateCallSubtypeTestStub(kTestTypeOneArg, is_instance_lbl,
-                                     is_not_instance_lbl);
-}
-
-// Generates inlined check if 'type' is a type parameter or type itself
-// TypeTestABI::kInstanceReg: instance (preserved).
-SubtypeTestCachePtr FlowGraphCompiler::GenerateUninstantiatedTypeTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("UninstantiatedTypeTest");
-  ASSERT(!type.IsInstantiated());
-  ASSERT(!type.IsFunctionType());
-  // Skip check if destination is a dynamic type.
-  if (type.IsTypeParameter()) {
-    const TypeParameter& type_param = TypeParameter::Cast(type);
-    static_assert(TypeTestABI::kFunctionTypeArgumentsReg <
-                      TypeTestABI::kInstantiatorTypeArgumentsReg,
-                  "Should be ordered to load arguments with one instruction");
-    __ ldm(IA, SP,
-           (1 << TypeTestABI::kFunctionTypeArgumentsReg) |
-               (1 << TypeTestABI::kInstantiatorTypeArgumentsReg));
-    const Register kTypeArgumentsReg =
-        type_param.IsClassTypeParameter()
-            ? TypeTestABI::kInstantiatorTypeArgumentsReg
-            : TypeTestABI::kFunctionTypeArgumentsReg;
-    // Check if type arguments are null, i.e. equivalent to vector of dynamic.
-    __ CompareObject(kTypeArgumentsReg, Object::null_object());
-    __ b(is_instance_lbl, EQ);
-    __ ldr(
-        TypeTestABI::kScratchReg,
-        compiler::FieldAddress(kTypeArgumentsReg,
-                               compiler::target::TypeArguments::type_at_offset(
-                                   type_param.index())));
-    // R3: concrete type of type.
-    // Check if type argument is dynamic, Object?, or void.
-    __ CompareObject(TypeTestABI::kScratchReg, Object::dynamic_type());
-    __ b(is_instance_lbl, EQ);
-    __ CompareObject(
-        TypeTestABI::kScratchReg,
-        Type::ZoneHandle(zone(),
-                         isolate()->object_store()->nullable_object_type()));
-    __ b(is_instance_lbl, EQ);
-    __ CompareObject(TypeTestABI::kScratchReg, Object::void_type());
-    __ b(is_instance_lbl, EQ);
-
-    // For Smi check quickly against int and num interfaces.
-    compiler::Label not_smi;
-    __ tst(TypeTestABI::kInstanceReg,
-           compiler::Operand(kSmiTagMask));  // Value is Smi?
-    __ b(&not_smi, NE);
-    __ CompareObject(TypeTestABI::kScratchReg,
-                     Type::ZoneHandle(zone(), Type::IntType()));
-    __ b(is_instance_lbl, EQ);
-    __ CompareObject(TypeTestABI::kScratchReg,
-                     Type::ZoneHandle(zone(), Type::Number()));
-    __ b(is_instance_lbl, EQ);
-    // Smi can be handled by type test cache.
-    __ Bind(&not_smi);
-
-    const auto test_kind = GetTypeTestStubKindForTypeParameter(type_param);
-    return GenerateCallSubtypeTestStub(test_kind, is_instance_lbl,
-                                       is_not_instance_lbl);
-  }
-  if (type.IsType()) {
-    // Smi is FutureOr<T>, when T is a top type or int or num.
-    if (!type.IsFutureOrType()) {
-      __ BranchIfSmi(TypeTestABI::kInstanceReg, is_not_instance_lbl);
-    }
-    static_assert(TypeTestABI::kFunctionTypeArgumentsReg <
-                      TypeTestABI::kInstantiatorTypeArgumentsReg,
-                  "Should be ordered to load arguments with one instruction");
-    __ ldm(IA, SP,
-           (1 << TypeTestABI::kFunctionTypeArgumentsReg) |
-               (1 << TypeTestABI::kInstantiatorTypeArgumentsReg));
-    // Uninstantiated type class is known at compile time, but the type
-    // arguments are determined at runtime by the instantiator(s).
-    return GenerateCallSubtypeTestStub(kTestTypeFourArgs, is_instance_lbl,
-                                       is_not_instance_lbl);
-  }
-  return SubtypeTestCache::null();
-}
-
-// Generates function type check.
-//
-// See [GenerateUninstantiatedTypeTest] for calling convention.
-SubtypeTestCachePtr FlowGraphCompiler::GenerateFunctionTypeTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ BranchIfSmi(TypeTestABI::kInstanceReg, is_not_instance_lbl);
-  static_assert(TypeTestABI::kFunctionTypeArgumentsReg <
-                    TypeTestABI::kInstantiatorTypeArgumentsReg,
-                "Should be ordered to load arguments with one instruction");
-  __ ldm(IA, SP,
-         (1 << TypeTestABI::kFunctionTypeArgumentsReg) |
-             (1 << TypeTestABI::kInstantiatorTypeArgumentsReg));
-  // Uninstantiated type class is known at compile time, but the type
-  // arguments are determined at runtime by the instantiator(s).
-  return GenerateCallSubtypeTestStub(kTestTypeSixArgs, is_instance_lbl,
-                                     is_not_instance_lbl);
-}
-
-// Inputs:
-// - R0: instance being type checked (preserved).
-// - R2: optional instantiator type arguments (preserved).
-// - R1: optional function type arguments (preserved).
-// Clobbers R3, R4, R8, R9.
-// Returns:
-// - preserved instance in R0, optional instantiator type arguments in R2, and
-//   optional function type arguments in R1.
-// Note that this inlined code must be followed by the runtime_call code, as it
-// may fall through to it. Otherwise, this inline code will jump to the label
-// is_instance or to the label is_not_instance.
-SubtypeTestCachePtr FlowGraphCompiler::GenerateInlineInstanceof(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("InlineInstanceof");
-
-  if (type.IsFunctionType()) {
-    return GenerateFunctionTypeTest(token_pos, type, is_instance_lbl,
-                                    is_not_instance_lbl);
-  }
-
-  if (type.IsInstantiated()) {
-    const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
-    // A class equality check is only applicable with a dst type (not a
-    // function type) of a non-parameterized class or with a raw dst type of
-    // a parameterized class.
-    if (type_class.NumTypeArguments() > 0) {
-      return GenerateInstantiatedTypeWithArgumentsTest(
-          token_pos, type, is_instance_lbl, is_not_instance_lbl);
-      // Fall through to runtime call.
-    }
-    const bool has_fall_through = GenerateInstantiatedTypeNoArgumentsTest(
-        token_pos, type, is_instance_lbl, is_not_instance_lbl);
-    if (has_fall_through) {
-      // If test non-conclusive so far, try the inlined type-test cache.
-      // 'type' is known at compile time.
-      return GenerateSubtype1TestCacheLookup(
-          token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-    } else {
-      return SubtypeTestCache::null();
-    }
-  }
-  return GenerateUninstantiatedTypeTest(token_pos, type, is_instance_lbl,
-                                        is_not_instance_lbl);
-}
-
-// If instanceof type test cannot be performed successfully at compile time and
-// therefore eliminated, optimize it by adding inlined tests for:
-// - Null -> see comment below.
-// - Smi -> compile time subtype check (only if dst class is not parameterized).
-// - Class equality (only if class is not parameterized).
-// Inputs:
-// - R0: object.
-// - R2: instantiator type arguments or raw_null.
-// - R1: function type arguments or raw_null.
-// Returns:
-// - true or false in R0.
-void FlowGraphCompiler::GenerateInstanceOf(TokenPosition token_pos,
-                                           intptr_t deopt_id,
-                                           const AbstractType& type,
-                                           LocationSummary* locs) {
-  ASSERT(type.IsFinalized());
-  ASSERT(!type.IsTopTypeForInstanceOf());  // Already checked.
-  static_assert(TypeTestABI::kFunctionTypeArgumentsReg <
-                    TypeTestABI::kInstantiatorTypeArgumentsReg,
-                "Should be ordered to push arguments with one instruction");
-  __ PushList((1 << TypeTestABI::kInstantiatorTypeArgumentsReg) |
-              (1 << TypeTestABI::kFunctionTypeArgumentsReg));
-
-  compiler::Label is_instance, is_not_instance;
-  // 'null' is an instance of Null, Object*, Never*, void, and dynamic.
-  // In addition, 'null' is an instance of any nullable type.
-  // It is also an instance of FutureOr<T> if it is an instance of T.
-  const AbstractType& unwrapped_type =
-      AbstractType::Handle(type.UnwrapFutureOr());
-  if (!unwrapped_type.IsTypeParameter() || unwrapped_type.IsNullable()) {
-    // Only nullable type parameter remains nullable after instantiation.
-    // See NullIsInstanceOf().
-    __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
-    __ b((unwrapped_type.IsNullable() ||
-          (unwrapped_type.IsLegacy() && unwrapped_type.IsNeverType()))
-             ? &is_instance
-             : &is_not_instance,
-         EQ);
-  }
-
-  // Generate inline instanceof test.
-  SubtypeTestCache& test_cache = SubtypeTestCache::ZoneHandle(zone());
-  test_cache =
-      GenerateInlineInstanceof(token_pos, type, &is_instance, &is_not_instance);
-
-  // test_cache is null if there is no fall-through.
-  compiler::Label done;
-  if (!test_cache.IsNull()) {
-    // Generate runtime call.
-    static_assert(TypeTestABI::kFunctionTypeArgumentsReg <
-                      TypeTestABI::kInstantiatorTypeArgumentsReg,
-                  "Should be ordered to load arguments with one instruction");
-    __ ldm(IA, SP,
-           (1 << TypeTestABI::kFunctionTypeArgumentsReg) |
-               (1 << TypeTestABI::kInstantiatorTypeArgumentsReg));
-    __ LoadUniqueObject(TypeTestABI::kDstTypeReg, type);
-    __ LoadUniqueObject(TypeTestABI::kSubtypeTestCacheReg, test_cache);
-    GenerateStubCall(token_pos, StubCode::InstanceOf(),
-                     /*kind=*/PcDescriptorsLayout::kOther, locs, deopt_id);
-    __ b(&done);
-  }
-  __ Bind(&is_not_instance);
-  __ LoadObject(R0, Bool::Get(false));
-  __ b(&done);
-
-  __ Bind(&is_instance);
-  __ LoadObject(R0, Bool::Get(true));
-  __ Bind(&done);
-  // Remove instantiator type arguments and function type arguments.
-  __ Drop(2);
-}
-
 void FlowGraphCompiler::GenerateIndirectTTSCall(Register reg_to_call,
                                                 intptr_t sub_type_cache_index) {
   __ LoadField(
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc b/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
index 8bed93e..d841daf 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
@@ -214,364 +214,6 @@
   __ Bind(&fall_through);
 }
 
-// Jumps to labels 'is_instance' or 'is_not_instance' respectively, if
-// type test is conclusive, otherwise fallthrough if a type test could not
-// be completed.
-// R0: instance being type checked (preserved).
-// Clobbers R1, R2.
-SubtypeTestCachePtr
-FlowGraphCompiler::GenerateInstantiatedTypeWithArgumentsTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("InstantiatedTypeWithArgumentsTest");
-  ASSERT(type.IsInstantiated());
-  ASSERT(!type.IsFunctionType());
-  const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
-  ASSERT(type_class.NumTypeArguments() > 0);
-  const Type& smi_type = Type::Handle(zone(), Type::SmiType());
-  const bool smi_is_ok = smi_type.IsSubtypeOf(type, Heap::kOld);
-  // Fast case for type = FutureOr<int/num/top-type>.
-  __ BranchIfSmi(TypeTestABI::kInstanceReg,
-                 smi_is_ok ? is_instance_lbl : is_not_instance_lbl);
-
-  const intptr_t num_type_args = type_class.NumTypeArguments();
-  const intptr_t num_type_params = type_class.NumTypeParameters();
-  const intptr_t from_index = num_type_args - num_type_params;
-  const TypeArguments& type_arguments =
-      TypeArguments::ZoneHandle(zone(), type.arguments());
-  const bool is_raw_type = type_arguments.IsNull() ||
-                           type_arguments.IsRaw(from_index, num_type_params);
-  if (is_raw_type) {
-    const Register kClassIdReg = R2;
-    // dynamic type argument, check only classes.
-    __ LoadClassId(kClassIdReg, TypeTestABI::kInstanceReg);
-    __ CompareImmediate(kClassIdReg, type_class.id());
-    __ b(is_instance_lbl, EQ);
-    // List is a very common case.
-    if (IsListClass(type_class)) {
-      GenerateListTypeCheck(kClassIdReg, is_instance_lbl);
-    }
-    return GenerateSubtype1TestCacheLookup(
-        token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-  }
-  // If one type argument only, check if type argument is a top type.
-  if (type_arguments.Length() == 1) {
-    const AbstractType& tp_argument =
-        AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
-    if (tp_argument.IsTopTypeForSubtyping()) {
-      // Instance class test only necessary.
-      return GenerateSubtype1TestCacheLookup(
-          token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-    }
-  }
-  // Regular subtype test cache involving instance's type arguments.
-  return GenerateCallSubtypeTestStub(kTestTypeTwoArgs, is_instance_lbl,
-                                     is_not_instance_lbl);
-}
-
-// Testing against an instantiated type with no arguments, without
-// SubtypeTestCache.
-// R0: instance being type checked (preserved).
-// Clobbers R2, R3.
-// Returns true if there is a fallthrough.
-bool FlowGraphCompiler::GenerateInstantiatedTypeNoArgumentsTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("InstantiatedTypeNoArgumentsTest");
-  ASSERT(type.IsInstantiated());
-  ASSERT(!type.IsFunctionType());
-  const Class& type_class = Class::Handle(zone(), type.type_class());
-  ASSERT(type_class.NumTypeArguments() == 0);
-
-  // If instance is Smi, check directly.
-  const Class& smi_class = Class::Handle(zone(), Smi::Class());
-  if (Class::IsSubtypeOf(smi_class, Object::null_type_arguments(),
-                         Nullability::kNonNullable, type, Heap::kOld)) {
-    // Fast case for type = int/num/top-type.
-    __ BranchIfSmi(TypeTestABI::kInstanceReg, is_instance_lbl);
-  } else {
-    __ BranchIfSmi(TypeTestABI::kInstanceReg, is_not_instance_lbl);
-  }
-  const Register kClassIdReg = R2;
-  __ LoadClassId(kClassIdReg, TypeTestABI::kInstanceReg);
-  // Bool interface can be implemented only by core class Bool.
-  if (type.IsBoolType()) {
-    __ CompareImmediate(kClassIdReg, kBoolCid);
-    __ b(is_instance_lbl, EQ);
-    __ b(is_not_instance_lbl);
-    return false;
-  }
-  // Custom checking for numbers (Smi, Mint and Double).
-  // Note that instance is not Smi (checked above).
-  if (type.IsNumberType() || type.IsIntType() || type.IsDoubleType()) {
-    GenerateNumberTypeCheck(kClassIdReg, type, is_instance_lbl,
-                            is_not_instance_lbl);
-    return false;
-  }
-  if (type.IsStringType()) {
-    GenerateStringTypeCheck(kClassIdReg, is_instance_lbl, is_not_instance_lbl);
-    return false;
-  }
-  if (type.IsDartFunctionType()) {
-    // Check if instance is a closure.
-    __ CompareImmediate(kClassIdReg, kClosureCid);
-    __ b(is_instance_lbl, EQ);
-    return true;  // Fall through
-  }
-
-  // Fast case for cid-range based checks.
-  // Warning: This code destroys the contents of [kClassIdReg].
-  if (GenerateSubtypeRangeCheck(kClassIdReg, type_class, is_instance_lbl)) {
-    return false;
-  }
-
-  // Otherwise fallthrough, result non-conclusive.
-  return true;
-}
-
-// Uses SubtypeTestCache to store instance class and result.
-// R0: instance to test.
-// Clobbers R1-R5.
-// Immediate class test already done.
-// TODO(srdjan): Implement a quicker subtype check, as type test
-// arrays can grow too high, but they may be useful when optimizing
-// code (type-feedback).
-SubtypeTestCachePtr FlowGraphCompiler::GenerateSubtype1TestCacheLookup(
-    TokenPosition token_pos,
-    const Class& type_class,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("Subtype1TestCacheLookup");
-#if defined(DEBUG)
-  compiler::Label ok;
-  __ BranchIfNotSmi(TypeTestABI::kInstanceReg, &ok);
-  __ Breakpoint();
-  __ Bind(&ok);
-#endif
-  __ LoadClassId(TMP, TypeTestABI::kInstanceReg);
-  __ LoadClassById(R1, TMP);
-  // R1: instance class.
-  // Check immediate superclass equality. If type_class is Object, then testing
-  // supertype may yield a wrong result for Null in NNBD strong mode (because
-  // Null also extends Object).
-  if (!type_class.IsObjectClass() || !Isolate::Current()->null_safety()) {
-    __ LoadFieldFromOffset(R2, R1, Class::super_type_offset());
-    __ LoadFieldFromOffset(R2, R2, Type::type_class_id_offset());
-    __ CompareImmediate(R2, Smi::RawValue(type_class.id()));
-    __ b(is_instance_lbl, EQ);
-  }
-
-  return GenerateCallSubtypeTestStub(kTestTypeOneArg, is_instance_lbl,
-                                     is_not_instance_lbl);
-}
-
-// Generates inlined check if 'type' is a type parameter or type itself
-// TypeTestABI::kInstanceReg: instance (preserved).
-SubtypeTestCachePtr FlowGraphCompiler::GenerateUninstantiatedTypeTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("UninstantiatedTypeTest");
-  ASSERT(!type.IsInstantiated());
-  ASSERT(!type.IsFunctionType());
-  // Skip check if destination is a dynamic type.
-  if (type.IsTypeParameter()) {
-    const TypeParameter& type_param = TypeParameter::Cast(type);
-
-    // Get instantiator type args (high) and function type args (low).
-    __ ldp(TypeTestABI::kFunctionTypeArgumentsReg,
-           TypeTestABI::kInstantiatorTypeArgumentsReg,
-           compiler::Address(SP, 0 * kWordSize, compiler::Address::PairOffset));
-    const Register kTypeArgumentsReg =
-        type_param.IsClassTypeParameter()
-            ? TypeTestABI::kInstantiatorTypeArgumentsReg
-            : TypeTestABI::kFunctionTypeArgumentsReg;
-    // Check if type arguments are null, i.e. equivalent to vector of dynamic.
-    __ CompareObject(kTypeArgumentsReg, Object::null_object());
-    __ b(is_instance_lbl, EQ);
-    __ LoadFieldFromOffset(TypeTestABI::kScratchReg, kTypeArgumentsReg,
-                           TypeArguments::type_at_offset(type_param.index()));
-    // R3: concrete type of type.
-    // Check if type argument is dynamic, Object?, or void.
-    __ CompareObject(TypeTestABI::kScratchReg, Object::dynamic_type());
-    __ b(is_instance_lbl, EQ);
-    __ CompareObject(
-        TypeTestABI::kScratchReg,
-        Type::ZoneHandle(zone(),
-                         isolate()->object_store()->nullable_object_type()));
-    __ b(is_instance_lbl, EQ);
-    __ CompareObject(TypeTestABI::kScratchReg, Object::void_type());
-    __ b(is_instance_lbl, EQ);
-
-    // For Smi check quickly against int and num interfaces.
-    compiler::Label not_smi;
-    __ BranchIfNotSmi(TypeTestABI::kInstanceReg, &not_smi);
-    __ CompareObject(TypeTestABI::kScratchReg,
-                     Type::ZoneHandle(zone(), Type::IntType()));
-    __ b(is_instance_lbl, EQ);
-    __ CompareObject(TypeTestABI::kScratchReg,
-                     Type::ZoneHandle(zone(), Type::Number()));
-    __ b(is_instance_lbl, EQ);
-    // Smi can be handled by type test cache.
-    __ Bind(&not_smi);
-
-    const auto test_kind = GetTypeTestStubKindForTypeParameter(type_param);
-    return GenerateCallSubtypeTestStub(test_kind, is_instance_lbl,
-                                       is_not_instance_lbl);
-  }
-  if (type.IsType()) {
-    // Smi is FutureOr<T>, when T is a top type or int or num.
-    if (!type.IsFutureOrType()) {
-      __ BranchIfSmi(TypeTestABI::kInstanceReg, is_not_instance_lbl);
-    }
-    __ ldp(TypeTestABI::kFunctionTypeArgumentsReg,
-           TypeTestABI::kInstantiatorTypeArgumentsReg,
-           compiler::Address(SP, 0 * kWordSize, compiler::Address::PairOffset));
-    // Uninstantiated type class is known at compile time, but the type
-    // arguments are determined at runtime by the instantiator.
-    return GenerateCallSubtypeTestStub(kTestTypeFourArgs, is_instance_lbl,
-                                       is_not_instance_lbl);
-  }
-  return SubtypeTestCache::null();
-}
-
-// Generates function type check.
-//
-// See [GenerateUninstantiatedTypeTest] for calling convention.
-SubtypeTestCachePtr FlowGraphCompiler::GenerateFunctionTypeTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ BranchIfSmi(TypeTestABI::kInstanceReg, is_not_instance_lbl);
-  __ ldp(TypeTestABI::kFunctionTypeArgumentsReg,
-         TypeTestABI::kInstantiatorTypeArgumentsReg,
-         compiler::Address(SP, 0 * kWordSize, compiler::Address::PairOffset));
-  // Uninstantiated type class is known at compile time, but the type
-  // arguments are determined at runtime by the instantiator(s).
-  return GenerateCallSubtypeTestStub(kTestTypeSixArgs, is_instance_lbl,
-                                     is_not_instance_lbl);
-}
-
-// Inputs:
-// - R0: instance being type checked (preserved).
-// - R2: optional instantiator type arguments (preserved).
-// - R1: optional function type arguments (preserved).
-// Clobbers R3, R4, R8, R9.
-// Returns:
-// - preserved instance in R0, optional instantiator type arguments in R2, and
-//   optional function type arguments in R1.
-// Note that this inlined code must be followed by the runtime_call code, as it
-// may fall through to it. Otherwise, this inline code will jump to the label
-// is_instance or to the label is_not_instance.
-SubtypeTestCachePtr FlowGraphCompiler::GenerateInlineInstanceof(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("InlineInstanceof");
-
-  if (type.IsFunctionType()) {
-    return GenerateFunctionTypeTest(token_pos, type, is_instance_lbl,
-                                    is_not_instance_lbl);
-  }
-
-  if (type.IsInstantiated()) {
-    const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
-    // A class equality check is only applicable with a dst type (not a
-    // function type) of a non-parameterized class or with a raw dst type of
-    // a parameterized class.
-    if (type_class.NumTypeArguments() > 0) {
-      return GenerateInstantiatedTypeWithArgumentsTest(
-          token_pos, type, is_instance_lbl, is_not_instance_lbl);
-      // Fall through to runtime call.
-    }
-    const bool has_fall_through = GenerateInstantiatedTypeNoArgumentsTest(
-        token_pos, type, is_instance_lbl, is_not_instance_lbl);
-    if (has_fall_through) {
-      // If test non-conclusive so far, try the inlined type-test cache.
-      // 'type' is known at compile time.
-      return GenerateSubtype1TestCacheLookup(
-          token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-    } else {
-      return SubtypeTestCache::null();
-    }
-  }
-  return GenerateUninstantiatedTypeTest(token_pos, type, is_instance_lbl,
-                                        is_not_instance_lbl);
-}
-
-// If instanceof type test cannot be performed successfully at compile time and
-// therefore eliminated, optimize it by adding inlined tests for:
-// - Null -> see comment below.
-// - Smi -> compile time subtype check (only if dst class is not parameterized).
-// - Class equality (only if class is not parameterized).
-// Inputs:
-// - R0: object.
-// - R2: instantiator type arguments or raw_null.
-// - R1: function type arguments or raw_null.
-// Returns:
-// - true or false in R0.
-void FlowGraphCompiler::GenerateInstanceOf(TokenPosition token_pos,
-                                           intptr_t deopt_id,
-                                           const AbstractType& type,
-                                           LocationSummary* locs) {
-  ASSERT(type.IsFinalized());
-  ASSERT(!type.IsTopTypeForInstanceOf());  // Already checked.
-  __ PushPair(TypeTestABI::kFunctionTypeArgumentsReg,
-              TypeTestABI::kInstantiatorTypeArgumentsReg);
-
-  compiler::Label is_instance, is_not_instance;
-  // 'null' is an instance of Null, Object*, Never*, void, and dynamic.
-  // In addition, 'null' is an instance of any nullable type.
-  // It is also an instance of FutureOr<T> if it is an instance of T.
-  const AbstractType& unwrapped_type =
-      AbstractType::Handle(type.UnwrapFutureOr());
-  if (!unwrapped_type.IsTypeParameter() || unwrapped_type.IsNullable()) {
-    // Only nullable type parameter remains nullable after instantiation.
-    // See NullIsInstanceOf().
-    __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
-    __ b((unwrapped_type.IsNullable() ||
-          (unwrapped_type.IsLegacy() && unwrapped_type.IsNeverType()))
-             ? &is_instance
-             : &is_not_instance,
-         EQ);
-  }
-
-  // Generate inline instanceof test.
-  SubtypeTestCache& test_cache = SubtypeTestCache::ZoneHandle(zone());
-  test_cache =
-      GenerateInlineInstanceof(token_pos, type, &is_instance, &is_not_instance);
-
-  // test_cache is null if there is no fall-through.
-  compiler::Label done;
-  if (!test_cache.IsNull()) {
-    // Generate runtime call.
-    __ ldp(TypeTestABI::kFunctionTypeArgumentsReg,
-           TypeTestABI::kInstantiatorTypeArgumentsReg,
-           compiler::Address(SP, 0 * kWordSize, compiler::Address::PairOffset));
-    __ LoadUniqueObject(TypeTestABI::kDstTypeReg, type);
-    __ LoadUniqueObject(TypeTestABI::kSubtypeTestCacheReg, test_cache);
-    GenerateStubCall(token_pos, StubCode::InstanceOf(),
-                     /*kind=*/PcDescriptorsLayout::kOther, locs, deopt_id);
-    __ b(&done);
-  }
-  __ Bind(&is_not_instance);
-  __ LoadObject(R0, Bool::Get(false));
-  __ b(&done);
-
-  __ Bind(&is_instance);
-  __ LoadObject(R0, Bool::Get(true));
-  __ Bind(&done);
-  // Remove instantiator type arguments and function type arguments.
-  __ Drop(2);
-}
-
 void FlowGraphCompiler::GenerateIndirectTTSCall(Register reg_to_call,
                                                 intptr_t sub_type_cache_index) {
   __ LoadField(
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc b/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
index a7d6a6e..c03a43a 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
@@ -197,10 +197,11 @@
 
 // Input registers (from TypeTestABI):
 // - kInstanceReg: instance.
+// - kDstTypeReg: destination type (for test_kind != kTestTypeOneArg).
 // - kInstantiatorTypeArgumentsReg: instantiator type arguments
-//   (for test_kind == kTestTypeFourArg or test_kind == kTestTypeSixArg).
+//   (for test_kind == kTestTypeFiveArg or test_kind == kTestTypeSevenArg).
 // - kFunctionTypeArgumentsReg: function type arguments
-//   (for test_kind == kTestTypeFourArg or test_kind == kTestTypeSixArg).
+//   (for test_kind == kTestTypeFiveArg or test_kind == kTestTypeSevenArg).
 //
 // Only preserves kInstanceReg from TypeTestABI, all other TypeTestABI
 // registers may be used and thus must be saved by the caller.
@@ -216,23 +217,34 @@
   if (test_kind == kTestTypeOneArg) {
     __ PushObject(Object::null_object());
     __ PushObject(Object::null_object());
+    __ PushObject(Object::null_object());
     __ Call(StubCode::Subtype1TestCache());
-  } else if (test_kind == kTestTypeTwoArgs) {
+  } else if (test_kind == kTestTypeThreeArgs) {
+    __ pushl(TypeTestABI::kDstTypeReg);
     __ PushObject(Object::null_object());
     __ PushObject(Object::null_object());
-    __ Call(StubCode::Subtype2TestCache());
-  } else if (test_kind == kTestTypeFourArgs) {
+    __ Call(StubCode::Subtype3TestCache());
+  } else if (test_kind == kTestTypeFiveArgs) {
+    __ pushl(TypeTestABI::kDstTypeReg);
     __ pushl(TypeTestABI::kInstantiatorTypeArgumentsReg);
     __ pushl(TypeTestABI::kFunctionTypeArgumentsReg);
-    __ Call(StubCode::Subtype4TestCache());
-  } else if (test_kind == kTestTypeSixArgs) {
+    __ Call(StubCode::Subtype5TestCache());
+  } else if (test_kind == kTestTypeSevenArgs) {
+    __ pushl(TypeTestABI::kDstTypeReg);
     __ pushl(TypeTestABI::kInstantiatorTypeArgumentsReg);
     __ pushl(TypeTestABI::kFunctionTypeArgumentsReg);
-    __ Call(StubCode::Subtype6TestCache());
+    __ Call(StubCode::Subtype7TestCache());
   } else {
     UNREACHABLE();
   }
-  __ Drop(2);
+  // Restore all but kSubtypeTestCacheReg (since it is the same as
+  // kSubtypeTestCacheResultReg).
+  static_assert(TypeTestABI::kSubtypeTestCacheReg ==
+                    TypeTestABI::kSubtypeTestCacheResultReg,
+                "Code assumes cache and result register are the same");
+  __ popl(TypeTestABI::kFunctionTypeArgumentsReg);
+  __ popl(TypeTestABI::kInstantiatorTypeArgumentsReg);
+  __ popl(TypeTestABI::kDstTypeReg);
   __ popl(TypeTestABI::kInstanceReg);  // Restore receiver.
   __ Drop(1);
   GenerateBoolToJump(TypeTestABI::kSubtypeTestCacheResultReg, is_instance_lbl,
@@ -240,313 +252,6 @@
   return type_test_cache.raw();
 }
 
-// Jumps to labels 'is_instance' or 'is_not_instance' respectively, if
-// type test is conclusive, otherwise fallthrough if a type test could not
-// be completed.
-// EAX: instance (must survive).
-// Clobbers ECX, EDI.
-SubtypeTestCachePtr
-FlowGraphCompiler::GenerateInstantiatedTypeWithArgumentsTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("InstantiatedTypeWithArgumentsTest");
-  ASSERT(type.IsInstantiated());
-  ASSERT(!type.IsFunctionType());
-  const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
-  ASSERT(type_class.NumTypeArguments() > 0);
-  const Type& smi_type = Type::Handle(zone(), Type::SmiType());
-  const bool smi_is_ok = smi_type.IsSubtypeOf(type, Heap::kOld);
-  __ testl(TypeTestABI::kInstanceReg, compiler::Immediate(kSmiTagMask));
-  if (smi_is_ok) {
-    // Fast case for type = FutureOr<int/num/top-type>.
-    __ j(ZERO, is_instance_lbl);
-  } else {
-    __ j(ZERO, is_not_instance_lbl);
-  }
-  const intptr_t num_type_args = type_class.NumTypeArguments();
-  const intptr_t num_type_params = type_class.NumTypeParameters();
-  const intptr_t from_index = num_type_args - num_type_params;
-  const TypeArguments& type_arguments =
-      TypeArguments::ZoneHandle(zone(), type.arguments());
-  const bool is_raw_type = type_arguments.IsNull() ||
-                           type_arguments.IsRaw(from_index, num_type_params);
-  if (is_raw_type) {
-    const Register kClassIdReg = ECX;
-    // dynamic type argument, check only classes.
-    __ LoadClassId(kClassIdReg, TypeTestABI::kInstanceReg);
-    __ cmpl(kClassIdReg, compiler::Immediate(type_class.id()));
-    __ j(EQUAL, is_instance_lbl);
-    // List is a very common case.
-    if (IsListClass(type_class)) {
-      GenerateListTypeCheck(kClassIdReg, is_instance_lbl);
-    }
-    return GenerateSubtype1TestCacheLookup(
-        token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-  }
-  // If one type argument only, check if type argument is a top type.
-  if (type_arguments.Length() == 1) {
-    const AbstractType& tp_argument =
-        AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
-    if (tp_argument.IsTopTypeForSubtyping()) {
-      // Instance class test only necessary.
-      return GenerateSubtype1TestCacheLookup(
-          token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-    }
-  }
-  // Regular subtype test cache involving instance's type arguments.
-  return GenerateCallSubtypeTestStub(kTestTypeTwoArgs, is_instance_lbl,
-                                     is_not_instance_lbl);
-}
-
-// Testing against an instantiated type with no arguments, without
-// SubtypeTestCache.
-// EAX: instance to test against (preserved).
-// Clobbers ECX, EDI.
-// Returns true if there is a fallthrough.
-bool FlowGraphCompiler::GenerateInstantiatedTypeNoArgumentsTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("InstantiatedTypeNoArgumentsTest");
-  ASSERT(type.IsInstantiated());
-  ASSERT(!type.IsFunctionType());
-  const Class& type_class = Class::Handle(zone(), type.type_class());
-  ASSERT(type_class.NumTypeArguments() == 0);
-
-  __ testl(TypeTestABI::kInstanceReg, compiler::Immediate(kSmiTagMask));
-  // If instance is Smi, check directly.
-  const Class& smi_class = Class::Handle(zone(), Smi::Class());
-  if (Class::IsSubtypeOf(smi_class, Object::null_type_arguments(),
-                         Nullability::kNonNullable, type, Heap::kOld)) {
-    // Fast case for type = int/num/top-type.
-    __ j(ZERO, is_instance_lbl);
-  } else {
-    __ j(ZERO, is_not_instance_lbl);
-  }
-  const Register kClassIdReg = ECX;
-  __ LoadClassId(kClassIdReg, TypeTestABI::kInstanceReg);
-  // Bool interface can be implemented only by core class Bool.
-  if (type.IsBoolType()) {
-    __ cmpl(kClassIdReg, compiler::Immediate(kBoolCid));
-    __ j(EQUAL, is_instance_lbl);
-    __ jmp(is_not_instance_lbl);
-    return false;
-  }
-  // Custom checking for numbers (Smi, Mint and Double).
-  // Note that instance is not Smi (checked above).
-  if (type.IsNumberType() || type.IsIntType() || type.IsDoubleType()) {
-    GenerateNumberTypeCheck(kClassIdReg, type, is_instance_lbl,
-                            is_not_instance_lbl);
-    return false;
-  }
-  if (type.IsStringType()) {
-    GenerateStringTypeCheck(kClassIdReg, is_instance_lbl, is_not_instance_lbl);
-    return false;
-  }
-  if (type.IsDartFunctionType()) {
-    // Check if instance is a closure.
-    __ cmpl(kClassIdReg, compiler::Immediate(kClosureCid));
-    __ j(EQUAL, is_instance_lbl);
-    return true;  // Fall through
-  }
-
-  // Fast case for cid-range based checks.
-  // Warning: This code destroys the contents of [kClassIdReg].
-  if (GenerateSubtypeRangeCheck(kClassIdReg, type_class, is_instance_lbl)) {
-    return false;
-  }
-
-  // Otherwise fallthrough, result non-conclusive.
-  return true;
-}
-
-// Uses SubtypeTestCache to store instance class and result.
-// EAX: instance to test.
-// Clobbers EDI, ECX.
-// Immediate class test already done.
-// TODO(srdjan): Implement a quicker subtype check, as type test
-// arrays can grow too high, but they may be useful when optimizing
-// code (type-feedback).
-SubtypeTestCachePtr FlowGraphCompiler::GenerateSubtype1TestCacheLookup(
-    TokenPosition token_pos,
-    const Class& type_class,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("Subtype1TestCacheLookup");
-#if defined(DEBUG)
-  compiler::Label ok;
-  __ BranchIfNotSmi(TypeTestABI::kInstanceReg, &ok);
-  __ Breakpoint();
-  __ Bind(&ok);
-#endif
-  __ LoadClassId(EDI, TypeTestABI::kInstanceReg);
-  __ LoadClassById(ECX, EDI);
-  // ECX: instance class.
-  // Check immediate superclass equality. If type_class is Object, then testing
-  // supertype may yield a wrong result for Null in NNBD strong mode (because
-  // Null also extends Object).
-  if (!type_class.IsObjectClass() || !Isolate::Current()->null_safety()) {
-    __ movl(EDI, compiler::FieldAddress(ECX, Class::super_type_offset()));
-    __ movl(EDI, compiler::FieldAddress(EDI, Type::type_class_id_offset()));
-    __ cmpl(EDI, compiler::Immediate(Smi::RawValue(type_class.id())));
-    __ j(EQUAL, is_instance_lbl);
-  }
-
-  return GenerateCallSubtypeTestStub(kTestTypeOneArg, is_instance_lbl,
-                                     is_not_instance_lbl);
-}
-
-// Generates inlined check if 'type' is a type parameter or type itself
-// EAX: instance (preserved).
-// Clobbers EDX, EDI, ECX.
-SubtypeTestCachePtr FlowGraphCompiler::GenerateUninstantiatedTypeTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("UninstantiatedTypeTest");
-  ASSERT(!type.IsInstantiated());
-  ASSERT(!type.IsFunctionType());
-  // Skip check if destination is a dynamic type.
-  const compiler::Immediate& raw_null =
-      compiler::Immediate(static_cast<intptr_t>(Object::null()));
-  if (type.IsTypeParameter()) {
-    const Register kTempReg = EDI;
-    const TypeParameter& type_param = TypeParameter::Cast(type);
-
-    __ movl(
-        TypeTestABI::kInstantiatorTypeArgumentsReg,
-        compiler::Address(ESP, 1 * kWordSize));  // Get instantiator type args.
-    __ movl(TypeTestABI::kFunctionTypeArgumentsReg,
-            compiler::Address(ESP, 0 * kWordSize));  // Get function type args.
-    // EDX: instantiator type arguments.
-    // ECX: function type arguments.
-    const Register kTypeArgumentsReg =
-        type_param.IsClassTypeParameter()
-            ? TypeTestABI::kInstantiatorTypeArgumentsReg
-            : TypeTestABI::kFunctionTypeArgumentsReg;
-    // Check if type arguments are null, i.e. equivalent to vector of dynamic.
-    __ cmpl(kTypeArgumentsReg, raw_null);
-    __ j(EQUAL, is_instance_lbl);
-    __ movl(kTempReg, compiler::FieldAddress(
-                          kTypeArgumentsReg,
-                          TypeArguments::type_at_offset(type_param.index())));
-    // EDI: concrete type of type.
-    // Check if type argument is dynamic, Object?, or void.
-    __ CompareObject(kTempReg, Object::dynamic_type());
-    __ j(EQUAL, is_instance_lbl);
-    __ CompareObject(
-        kTempReg,
-        Type::ZoneHandle(zone(),
-                         isolate()->object_store()->nullable_object_type()));
-    __ j(EQUAL, is_instance_lbl);
-    __ CompareObject(kTempReg, Object::void_type());
-    __ j(EQUAL, is_instance_lbl);
-
-    // For Smi check quickly against int and num interfaces.
-    compiler::Label not_smi;
-    __ testl(TypeTestABI::kInstanceReg,
-             compiler::Immediate(kSmiTagMask));  // Value is Smi?
-    __ j(NOT_ZERO, &not_smi, compiler::Assembler::kNearJump);
-    __ CompareObject(kTempReg, Type::ZoneHandle(zone(), Type::IntType()));
-    __ j(EQUAL, is_instance_lbl);
-    __ CompareObject(kTempReg, Type::ZoneHandle(zone(), Type::Number()));
-    __ j(EQUAL, is_instance_lbl);
-    // Smi can be handled by type test cache.
-    __ Bind(&not_smi);
-
-    const auto test_kind = GetTypeTestStubKindForTypeParameter(type_param);
-    return GenerateCallSubtypeTestStub(test_kind, is_instance_lbl,
-                                       is_not_instance_lbl);
-  }
-  if (type.IsType()) {
-    // Smi is FutureOr<T>, when T is a top type or int or num.
-    if (!type.IsFutureOrType()) {
-      __ testl(TypeTestABI::kInstanceReg,
-               compiler::Immediate(kSmiTagMask));  // Is instance Smi?
-      __ j(ZERO, is_not_instance_lbl);
-    }
-    __ movl(TypeTestABI::kInstantiatorTypeArgumentsReg,
-            compiler::Address(ESP, 1 * kWordSize));
-    __ movl(TypeTestABI::kFunctionTypeArgumentsReg,
-            compiler::Address(ESP, 0 * kWordSize));
-    // Uninstantiated type class is known at compile time, but the type
-    // arguments are determined at runtime by the instantiator(s).
-    return GenerateCallSubtypeTestStub(kTestTypeFourArgs, is_instance_lbl,
-                                       is_not_instance_lbl);
-  }
-  return SubtypeTestCache::null();
-}
-
-// Generates function type check.
-//
-// See [GenerateUninstantiatedTypeTest] for calling convention.
-SubtypeTestCachePtr FlowGraphCompiler::GenerateFunctionTypeTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("FunctionTypeTest");
-
-  __ testl(TypeTestABI::kInstanceReg, compiler::Immediate(kSmiTagMask));
-  __ j(ZERO, is_not_instance_lbl);
-  // Uninstantiated type class is known at compile time, but the type
-  // arguments are determined at runtime by the instantiator(s).
-  return GenerateCallSubtypeTestStub(kTestTypeSixArgs, is_instance_lbl,
-                                     is_not_instance_lbl);
-}
-
-// Inputs:
-// - EAX: instance to test against (preserved).
-// - EDX: optional instantiator type arguments (preserved).
-// - ECX: optional function type arguments (preserved).
-// Clobbers EDI.
-// Returns:
-// - preserved instance in EAX, optional instantiator type arguments in EDX, and
-//   optional function type arguments in RCX.
-// Note that this inlined code must be followed by the runtime_call code, as it
-// may fall through to it. Otherwise, this inline code will jump to the label
-// is_instance or to the label is_not_instance.
-SubtypeTestCachePtr FlowGraphCompiler::GenerateInlineInstanceof(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("InlineInstanceof");
-
-  if (type.IsFunctionType()) {
-    return GenerateFunctionTypeTest(token_pos, type, is_instance_lbl,
-                                    is_not_instance_lbl);
-  }
-
-  if (type.IsInstantiated()) {
-    const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
-    // A class equality check is only applicable with a dst type (not a
-    // function type) of a non-parameterized class or with a raw dst type of
-    // a parameterized class.
-    if (type_class.NumTypeArguments() > 0) {
-      return GenerateInstantiatedTypeWithArgumentsTest(
-          token_pos, type, is_instance_lbl, is_not_instance_lbl);
-      // Fall through to runtime call.
-    }
-    const bool has_fall_through = GenerateInstantiatedTypeNoArgumentsTest(
-        token_pos, type, is_instance_lbl, is_not_instance_lbl);
-    if (has_fall_through) {
-      // If test non-conclusive so far, try the inlined type-test cache.
-      // 'type' is known at compile time.
-      return GenerateSubtype1TestCacheLookup(
-          token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-    } else {
-      return SubtypeTestCache::null();
-    }
-  }
-  return GenerateUninstantiatedTypeTest(token_pos, type, is_instance_lbl,
-                                        is_not_instance_lbl);
-}
-
 // If instanceof type test cannot be performed successfully at compile time and
 // therefore eliminated, optimize it by adding inlined tests for:
 // - Null -> see comment below.
@@ -565,9 +270,6 @@
   ASSERT(type.IsFinalized());
   ASSERT(!type.IsTopTypeForInstanceOf());  // Already checked.
 
-  __ pushl(EDX);  // Store instantiator type arguments.
-  __ pushl(ECX);  // Store function type arguments.
-
   const compiler::Immediate& raw_null =
       compiler::Immediate(static_cast<intptr_t>(Object::null()));
   compiler::Label is_instance, is_not_instance;
@@ -595,33 +297,28 @@
   compiler::Label done;
   if (!test_cache.IsNull()) {
     // Generate runtime call.
-    __ movl(EDX, compiler::Address(
-                     ESP, 1 * kWordSize));  // Get instantiator type args.
-    __ movl(ECX,
-            compiler::Address(ESP, 0 * kWordSize));  // Get function type args.
     __ PushObject(Object::null_object());  // Make room for the result.
-    __ pushl(EAX);                         // Push the instance.
+    __ pushl(TypeTestABI::kInstanceReg);   // Push the instance.
     __ PushObject(type);                   // Push the type.
     __ pushl(TypeTestABI::kInstantiatorTypeArgumentsReg);
     __ pushl(TypeTestABI::kFunctionTypeArgumentsReg);
-    __ LoadObject(EAX, test_cache);
-    __ pushl(EAX);
+    // Can reuse kInstanceReg as scratch here since it was pushed above.
+    __ LoadObject(TypeTestABI::kInstanceReg, test_cache);
+    __ pushl(TypeTestABI::kInstanceReg);
     GenerateRuntimeCall(token_pos, deopt_id, kInstanceofRuntimeEntry, 5, locs);
     // Pop the parameters supplied to the runtime entry. The result of the
     // instanceof runtime call will be left as the result of the operation.
     __ Drop(5);
-    __ popl(EAX);
+    __ popl(TypeTestABI::kInstanceOfResultReg);
     __ jmp(&done, compiler::Assembler::kNearJump);
   }
   __ Bind(&is_not_instance);
-  __ LoadObject(EAX, Bool::Get(false));
+  __ LoadObject(TypeTestABI::kInstanceOfResultReg, Bool::Get(false));
   __ jmp(&done, compiler::Assembler::kNearJump);
 
   __ Bind(&is_instance);
-  __ LoadObject(EAX, Bool::Get(true));
+  __ LoadObject(TypeTestABI::kInstanceOfResultReg, Bool::Get(true));
   __ Bind(&done);
-  __ popl(ECX);  // Remove pushed function type arguments.
-  __ popl(EDX);  // Remove pushed instantiator type arguments.
 }
 
 // Optimize assignable type check by adding inlined tests for:
@@ -656,9 +353,6 @@
 
   if (dst_type.IsTopTypeForSubtyping()) return;  // No code needed.
 
-  __ pushl(TypeTestABI::kInstantiatorTypeArgumentsReg);
-  __ pushl(TypeTestABI::kFunctionTypeArgumentsReg);
-
   compiler::Label is_assignable, runtime_call;
   if (Instance::NullIsAssignableTo(dst_type)) {
     const compiler::Immediate& raw_null =
@@ -673,11 +367,6 @@
                                         &runtime_call);
 
   __ Bind(&runtime_call);
-  __ movl(
-      TypeTestABI::kInstantiatorTypeArgumentsReg,
-      compiler::Address(ESP, 1 * kWordSize));  // Get instantiator type args.
-  __ movl(TypeTestABI::kFunctionTypeArgumentsReg,
-          compiler::Address(ESP, 0 * kWordSize));  // Get function type args.
   __ PushObject(Object::null_object());            // Make room for the result.
   __ pushl(TypeTestABI::kInstanceReg);             // Push the source object.
   if (locs->in(1).IsConstant()) {
@@ -689,8 +378,9 @@
   __ pushl(TypeTestABI::kInstantiatorTypeArgumentsReg);
   __ pushl(TypeTestABI::kFunctionTypeArgumentsReg);
   __ PushObject(dst_name);  // Push the name of the destination.
-  __ LoadObject(EAX, test_cache);
-  __ pushl(EAX);
+  // Can reuse kInstanceReg as scratch here since it was pushed above.
+  __ LoadObject(TypeTestABI::kInstanceReg, test_cache);
+  __ pushl(TypeTestABI::kInstanceReg);
   __ PushObject(Smi::ZoneHandle(zone(), Smi::New(kTypeCheckFromInline)));
   GenerateRuntimeCall(token_pos, deopt_id, kTypeCheckRuntimeEntry, 7, locs);
   // Pop the parameters supplied to the runtime entry. The result of the
@@ -699,8 +389,6 @@
   __ popl(TypeTestABI::kInstanceReg);
 
   __ Bind(&is_assignable);
-  __ popl(TypeTestABI::kFunctionTypeArgumentsReg);
-  __ popl(TypeTestABI::kInstantiatorTypeArgumentsReg);
 }
 
 void FlowGraphCompiler::EmitInstructionEpilogue(Instruction* instr) {
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc b/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
index 62efb18..aad34de 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
@@ -217,375 +217,6 @@
   __ Bind(&fall_through);
 }
 
-// Jumps to labels 'is_instance' or 'is_not_instance' respectively, if
-// type test is conclusive, otherwise fallthrough if a type test could not
-// be completed.
-// RAX: instance (must survive).
-// Clobbers R10.
-SubtypeTestCachePtr
-FlowGraphCompiler::GenerateInstantiatedTypeWithArgumentsTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("InstantiatedTypeWithArgumentsTest");
-  ASSERT(type.IsInstantiated());
-  ASSERT(!type.IsFunctionType());
-  const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
-  ASSERT(type_class.NumTypeArguments() > 0);
-  const Type& smi_type = Type::Handle(zone(), Type::SmiType());
-  const bool smi_is_ok = smi_type.IsSubtypeOf(type, Heap::kOld);
-  __ testq(TypeTestABI::kInstanceReg, compiler::Immediate(kSmiTagMask));
-  if (smi_is_ok) {
-    // Fast case for type = FutureOr<int/num/top-type>.
-    __ j(ZERO, is_instance_lbl);
-  } else {
-    __ j(ZERO, is_not_instance_lbl);
-  }
-
-  const intptr_t num_type_args = type_class.NumTypeArguments();
-  const intptr_t num_type_params = type_class.NumTypeParameters();
-  const intptr_t from_index = num_type_args - num_type_params;
-  const TypeArguments& type_arguments =
-      TypeArguments::ZoneHandle(zone(), type.arguments());
-  const bool is_raw_type = type_arguments.IsNull() ||
-                           type_arguments.IsRaw(from_index, num_type_params);
-  if (is_raw_type) {
-    const Register kClassIdReg = R10;
-    // dynamic type argument, check only classes.
-    __ LoadClassId(kClassIdReg, TypeTestABI::kInstanceReg);
-    __ cmpl(kClassIdReg, compiler::Immediate(type_class.id()));
-    __ j(EQUAL, is_instance_lbl);
-    // List is a very common case.
-    if (IsListClass(type_class)) {
-      GenerateListTypeCheck(kClassIdReg, is_instance_lbl);
-    }
-    return GenerateSubtype1TestCacheLookup(
-        token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-  }
-  // If one type argument only, check if type argument is a top type.
-  if (type_arguments.Length() == 1) {
-    const AbstractType& tp_argument =
-        AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
-    if (tp_argument.IsTopTypeForSubtyping()) {
-      // Instance class test only necessary.
-      return GenerateSubtype1TestCacheLookup(
-          token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-    }
-  }
-
-  // Regular subtype test cache involving instance's type arguments.
-  return GenerateCallSubtypeTestStub(kTestTypeTwoArgs, is_instance_lbl,
-                                     is_not_instance_lbl);
-}
-
-// Testing against an instantiated type with no arguments, without
-// SubtypeTestCache
-//
-// Inputs:
-//   - RAX : instance to test against
-//
-// Preserves RAX/RCX/RDX.
-//
-// Returns true if there is a fallthrough.
-bool FlowGraphCompiler::GenerateInstantiatedTypeNoArgumentsTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("InstantiatedTypeNoArgumentsTest");
-  ASSERT(type.IsInstantiated());
-  ASSERT(!type.IsFunctionType());
-  const Class& type_class = Class::Handle(zone(), type.type_class());
-  ASSERT(type_class.NumTypeArguments() == 0);
-
-  __ testq(TypeTestABI::kInstanceReg, compiler::Immediate(kSmiTagMask));
-  // If instance is Smi, check directly.
-  const Class& smi_class = Class::Handle(zone(), Smi::Class());
-  if (Class::IsSubtypeOf(smi_class, Object::null_type_arguments(),
-                         Nullability::kNonNullable, type, Heap::kOld)) {
-    // Fast case for type = int/num/top-type.
-    __ j(ZERO, is_instance_lbl);
-  } else {
-    __ j(ZERO, is_not_instance_lbl);
-  }
-  const Register kClassIdReg = R10;
-  __ LoadClassId(kClassIdReg, TypeTestABI::kInstanceReg);
-  // Bool interface can be implemented only by core class Bool.
-  if (type.IsBoolType()) {
-    __ cmpl(kClassIdReg, compiler::Immediate(kBoolCid));
-    __ j(EQUAL, is_instance_lbl);
-    __ jmp(is_not_instance_lbl);
-    return false;
-  }
-  // Custom checking for numbers (Smi, Mint and Double).
-  // Note that instance is not Smi (checked above).
-  if (type.IsNumberType() || type.IsIntType() || type.IsDoubleType()) {
-    GenerateNumberTypeCheck(kClassIdReg, type, is_instance_lbl,
-                            is_not_instance_lbl);
-    return false;
-  }
-  if (type.IsStringType()) {
-    GenerateStringTypeCheck(kClassIdReg, is_instance_lbl, is_not_instance_lbl);
-    return false;
-  }
-  if (type.IsDartFunctionType()) {
-    // Check if instance is a closure.
-    __ cmpq(kClassIdReg, compiler::Immediate(kClosureCid));
-    __ j(EQUAL, is_instance_lbl);
-    return true;
-  }
-
-  // Fast case for cid-range based checks.
-  // Warning: This code destroys the contents of [kClassIdReg].
-  if (GenerateSubtypeRangeCheck(kClassIdReg, type_class, is_instance_lbl)) {
-    return false;
-  }
-
-  // Otherwise fallthrough, result non-conclusive.
-  return true;
-}
-
-// Uses SubtypeTestCache to store instance class and result.
-// Immediate class test already done.
-//
-// Inputs:
-//   RAX : instance to test against.
-//
-// Preserves RAX/RCX/RDX.
-//
-// TODO(srdjan): Implement a quicker subtype check, as type test
-// arrays can grow too high, but they may be useful when optimizing
-// code (type-feedback).
-SubtypeTestCachePtr FlowGraphCompiler::GenerateSubtype1TestCacheLookup(
-    TokenPosition token_pos,
-    const Class& type_class,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("Subtype1TestCacheLookup");
-#if defined(DEBUG)
-  compiler::Label ok;
-  __ BranchIfNotSmi(TypeTestABI::kInstanceReg, &ok);
-  __ Breakpoint();
-  __ Bind(&ok);
-#endif
-  __ LoadClassId(TMP, TypeTestABI::kInstanceReg);
-  __ LoadClassById(R10, TMP);
-  // R10: instance class.
-  // Check immediate superclass equality. If type_class is Object, then testing
-  // supertype may yield a wrong result for Null in NNBD strong mode (because
-  // Null also extends Object).
-  if (!type_class.IsObjectClass() || !Isolate::Current()->null_safety()) {
-    __ movq(R13, compiler::FieldAddress(R10, Class::super_type_offset()));
-    __ movq(R13, compiler::FieldAddress(R13, Type::type_class_id_offset()));
-    __ CompareImmediate(R13,
-                        compiler::Immediate(Smi::RawValue(type_class.id())));
-    __ j(EQUAL, is_instance_lbl);
-  }
-
-  return GenerateCallSubtypeTestStub(kTestTypeOneArg, is_instance_lbl,
-                                     is_not_instance_lbl);
-}
-
-// Generates inlined check if 'type' is a type parameter or type itself
-//
-// Inputs (from TypeTestABI):
-//   - kInstanceReg : instance to test against.
-//   - kInstantiatorTypeArgumentsReg : instantiator type arguments
-//     (if necessary).
-//   - kFunctionTypeArgumentsReg : function type arguments (if necessary).
-//
-// Preserves all input registers.
-SubtypeTestCachePtr FlowGraphCompiler::GenerateUninstantiatedTypeTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("UninstantiatedTypeTest");
-  ASSERT(!type.IsInstantiated());
-  ASSERT(!type.IsFunctionType());
-  // Skip check if destination is a dynamic type.
-  if (type.IsTypeParameter()) {
-    const TypeParameter& type_param = TypeParameter::Cast(type);
-
-    const Register kTypeArgumentsReg =
-        type_param.IsClassTypeParameter()
-            ? TypeTestABI::kInstantiatorTypeArgumentsReg
-            : TypeTestABI::kFunctionTypeArgumentsReg;
-    // Check if type arguments are null, i.e. equivalent to vector of dynamic.
-    __ CompareObject(kTypeArgumentsReg, Object::null_object());
-    __ j(EQUAL, is_instance_lbl);
-    __ movq(TypeTestABI::kScratchReg,
-            compiler::FieldAddress(
-                kTypeArgumentsReg,
-                TypeArguments::type_at_offset(type_param.index())));
-    // RDI: Concrete type of type.
-    // Check if type argument is dynamic, Object?, or void.
-    __ CompareObject(TypeTestABI::kScratchReg, Object::dynamic_type());
-    __ j(EQUAL, is_instance_lbl);
-    __ CompareObject(
-        TypeTestABI::kScratchReg,
-        Type::ZoneHandle(zone(),
-                         isolate()->object_store()->nullable_object_type()));
-    __ j(EQUAL, is_instance_lbl);
-    __ CompareObject(TypeTestABI::kScratchReg, Object::void_type());
-    __ j(EQUAL, is_instance_lbl);
-
-    // For Smi check quickly against int and num interfaces.
-    compiler::Label not_smi;
-    __ testq(TypeTestABI::kInstanceReg,
-             compiler::Immediate(kSmiTagMask));  // Value is Smi?
-    __ j(NOT_ZERO, &not_smi, compiler::Assembler::kNearJump);
-    __ CompareObject(TypeTestABI::kScratchReg,
-                     Type::ZoneHandle(zone(), Type::IntType()));
-    __ j(EQUAL, is_instance_lbl);
-    __ CompareObject(TypeTestABI::kScratchReg,
-                     Type::ZoneHandle(zone(), Type::Number()));
-    __ j(EQUAL, is_instance_lbl);
-    // Smi can be handled by type test cache.
-    __ Bind(&not_smi);
-
-    const auto test_kind = GetTypeTestStubKindForTypeParameter(type_param);
-    return GenerateCallSubtypeTestStub(test_kind, is_instance_lbl,
-                                       is_not_instance_lbl);
-  }
-  if (type.IsType()) {
-    // Smi is FutureOr<T>, when T is a top type or int or num.
-    if (!type.IsFutureOrType()) {
-      __ testq(TypeTestABI::kInstanceReg,
-               compiler::Immediate(kSmiTagMask));  // Is instance Smi?
-      __ j(ZERO, is_not_instance_lbl);
-    }
-    // Uninstantiated type class is known at compile time, but the type
-    // arguments are determined at runtime by the instantiator(s).
-    return GenerateCallSubtypeTestStub(kTestTypeFourArgs, is_instance_lbl,
-                                       is_not_instance_lbl);
-  }
-  return SubtypeTestCache::null();
-}
-
-// Generates function type check.
-//
-// See [GenerateUninstantiatedTypeTest] for calling convention.
-SubtypeTestCachePtr FlowGraphCompiler::GenerateFunctionTypeTest(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("FunctionTypeTest");
-
-  __ testq(TypeTestABI::kInstanceReg, compiler::Immediate(kSmiTagMask));
-  __ j(ZERO, is_not_instance_lbl);
-  return GenerateCallSubtypeTestStub(kTestTypeSixArgs, is_instance_lbl,
-                                     is_not_instance_lbl);
-}
-
-// Inputs:
-//   - RAX : instance to test against.
-//   - RDX : instantiator type arguments.
-//   - RCX : function type arguments.
-//
-// Preserves RAX/RCX/RDX.
-//
-// Note that this inlined code must be followed by the runtime_call code, as it
-// may fall through to it. Otherwise, this inline code will jump to the label
-// is_instance or to the label is_not_instance.
-SubtypeTestCachePtr FlowGraphCompiler::GenerateInlineInstanceof(
-    TokenPosition token_pos,
-    const AbstractType& type,
-    compiler::Label* is_instance_lbl,
-    compiler::Label* is_not_instance_lbl) {
-  __ Comment("InlineInstanceof");
-
-  if (type.IsFunctionType()) {
-    return GenerateFunctionTypeTest(token_pos, type, is_instance_lbl,
-                                    is_not_instance_lbl);
-  }
-
-  if (type.IsInstantiated()) {
-    const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
-    // A class equality check is only applicable with a dst type (not a
-    // function type) of a non-parameterized class or with a raw dst type of
-    // a parameterized class.
-    if (type_class.NumTypeArguments() > 0) {
-      return GenerateInstantiatedTypeWithArgumentsTest(
-          token_pos, type, is_instance_lbl, is_not_instance_lbl);
-      // Fall through to runtime call.
-    }
-    const bool has_fall_through = GenerateInstantiatedTypeNoArgumentsTest(
-        token_pos, type, is_instance_lbl, is_not_instance_lbl);
-    if (has_fall_through) {
-      // If test non-conclusive so far, try the inlined type-test cache.
-      // 'type' is known at compile time.
-      return GenerateSubtype1TestCacheLookup(
-          token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-    } else {
-      return SubtypeTestCache::null();
-    }
-  }
-  return GenerateUninstantiatedTypeTest(token_pos, type, is_instance_lbl,
-                                        is_not_instance_lbl);
-}
-
-// If instanceof type test cannot be performed successfully at compile time and
-// therefore eliminated, optimize it by adding inlined tests for:
-// - Null -> see comment below.
-// - Smi -> compile time subtype check (only if dst class is not parameterized).
-// - Class equality (only if class is not parameterized).
-// Inputs:
-// - RAX: object.
-// - RDX: instantiator type arguments or raw_null.
-// - RCX: function type arguments or raw_null.
-// Returns:
-// - true or false in RAX.
-void FlowGraphCompiler::GenerateInstanceOf(TokenPosition token_pos,
-                                           intptr_t deopt_id,
-                                           const AbstractType& type,
-                                           LocationSummary* locs) {
-  ASSERT(type.IsFinalized());
-  ASSERT(!type.IsTopTypeForInstanceOf());  // Already checked.
-
-  compiler::Label is_instance, is_not_instance;
-  // 'null' is an instance of Null, Object*, Never*, void, and dynamic.
-  // In addition, 'null' is an instance of any nullable type.
-  // It is also an instance of FutureOr<T> if it is an instance of T.
-  const AbstractType& unwrapped_type =
-      AbstractType::Handle(type.UnwrapFutureOr());
-  if (!unwrapped_type.IsTypeParameter() || unwrapped_type.IsNullable()) {
-    // Only nullable type parameter remains nullable after instantiation.
-    // See NullIsInstanceOf().
-    __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
-    __ j(EQUAL, (unwrapped_type.IsNullable() ||
-                 (unwrapped_type.IsLegacy() && unwrapped_type.IsNeverType()))
-                    ? &is_instance
-                    : &is_not_instance);
-  }
-
-  // Generate inline instanceof test.
-  SubtypeTestCache& test_cache = SubtypeTestCache::ZoneHandle(zone());
-  // The registers RAX, RCX, RDX are preserved across the call.
-  test_cache =
-      GenerateInlineInstanceof(token_pos, type, &is_instance, &is_not_instance);
-
-  // test_cache is null if there is no fall-through.
-  compiler::Label done;
-  if (!test_cache.IsNull()) {
-    // Generate Runtime call.
-    __ LoadUniqueObject(TypeTestABI::kDstTypeReg, type);
-    __ LoadUniqueObject(TypeTestABI::kSubtypeTestCacheReg, test_cache);
-    GenerateStubCall(token_pos, StubCode::InstanceOf(),
-                     /*kind=*/PcDescriptorsLayout::kOther, locs, deopt_id);
-    __ jmp(&done, compiler::Assembler::kNearJump);
-  }
-  __ Bind(&is_not_instance);
-  __ LoadObject(RAX, Bool::Get(false));
-  __ jmp(&done, compiler::Assembler::kNearJump);
-
-  __ Bind(&is_instance);
-  __ LoadObject(RAX, Bool::Get(true));
-  __ Bind(&done);
-}
-
 void FlowGraphCompiler::GenerateIndirectTTSCall(Register reg_to_call,
                                                 intptr_t sub_type_cache_index) {
   __ LoadWordFromPoolIndex(TypeTestABI::kSubtypeTestCacheReg,
diff --git a/runtime/vm/compiler/runtime_api.cc b/runtime/vm/compiler/runtime_api.cc
index dbe6784..f2f0c92 100644
--- a/runtime/vm/compiler/runtime_api.cc
+++ b/runtime/vm/compiler/runtime_api.cc
@@ -252,12 +252,12 @@
   return dart::StubCode::AllocateArray();
 }
 
-const Code& StubCodeSubtype2TestCache() {
-  return dart::StubCode::Subtype2TestCache();
+const Code& StubCodeSubtype3TestCache() {
+  return dart::StubCode::Subtype3TestCache();
 }
 
-const Code& StubCodeSubtype6TestCache() {
-  return dart::StubCode::Subtype6TestCache();
+const Code& StubCodeSubtype7TestCache() {
+  return dart::StubCode::Subtype7TestCache();
 }
 
 #define DEFINE_ALIAS(name)                                                     \
diff --git a/runtime/vm/compiler/runtime_api.h b/runtime/vm/compiler/runtime_api.h
index b7ad7e2..90f6566 100644
--- a/runtime/vm/compiler/runtime_api.h
+++ b/runtime/vm/compiler/runtime_api.h
@@ -206,8 +206,8 @@
                                          intptr_t);
 
 const Code& StubCodeAllocateArray();
-const Code& StubCodeSubtype2TestCache();
-const Code& StubCodeSubtype6TestCache();
+const Code& StubCodeSubtype3TestCache();
+const Code& StubCodeSubtype7TestCache();
 
 class RuntimeEntry : public ValueObject {
  public:
@@ -1201,6 +1201,7 @@
 
   static const word kTestEntryLength;
   static const word kInstanceClassIdOrFunction;
+  static const word kDestinationType;
   static const word kInstanceTypeArguments;
   static const word kInstantiatorTypeArguments;
   static const word kFunctionTypeArguments;
diff --git a/runtime/vm/compiler/runtime_offsets_extracted.h b/runtime/vm/compiler/runtime_offsets_extracted.h
index f5b0be5..9cd38b3 100644
--- a/runtime/vm/compiler/runtime_offsets_extracted.h
+++ b/runtime/vm/compiler/runtime_offsets_extracted.h
@@ -45,19 +45,21 @@
     NativeEntry_kNumCallWrapperArguments = 2;
 static constexpr dart::compiler::target::word String_kMaxElements = 536870911;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kFunctionTypeArguments = 4;
+    SubtypeTestCache_kFunctionTypeArguments = 5;
 static constexpr dart::compiler::target::word
     SubtypeTestCache_kInstanceClassIdOrFunction = 1;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 6;
+    SubtypeTestCache_kDestinationType = 2;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 5;
+    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 7;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceTypeArguments = 2;
+    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 6;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstantiatorTypeArguments = 3;
+    SubtypeTestCache_kInstanceTypeArguments = 3;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kTestEntryLength = 7;
+    SubtypeTestCache_kInstantiatorTypeArguments = 4;
+static constexpr dart::compiler::target::word
+    SubtypeTestCache_kTestEntryLength = 8;
 static constexpr dart::compiler::target::word SubtypeTestCache_kTestResult = 0;
 static constexpr dart::compiler::target::word TypeArguments_kMaxElements =
     268435455;
@@ -559,19 +561,21 @@
 static constexpr dart::compiler::target::word String_kMaxElements =
     2305843009213693951;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kFunctionTypeArguments = 4;
+    SubtypeTestCache_kFunctionTypeArguments = 5;
 static constexpr dart::compiler::target::word
     SubtypeTestCache_kInstanceClassIdOrFunction = 1;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 6;
+    SubtypeTestCache_kDestinationType = 2;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 5;
+    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 7;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceTypeArguments = 2;
+    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 6;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstantiatorTypeArguments = 3;
+    SubtypeTestCache_kInstanceTypeArguments = 3;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kTestEntryLength = 7;
+    SubtypeTestCache_kInstantiatorTypeArguments = 4;
+static constexpr dart::compiler::target::word
+    SubtypeTestCache_kTestEntryLength = 8;
 static constexpr dart::compiler::target::word SubtypeTestCache_kTestResult = 0;
 static constexpr dart::compiler::target::word TypeArguments_kMaxElements =
     576460752303423487;
@@ -1077,19 +1081,21 @@
     NativeEntry_kNumCallWrapperArguments = 2;
 static constexpr dart::compiler::target::word String_kMaxElements = 536870911;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kFunctionTypeArguments = 4;
+    SubtypeTestCache_kFunctionTypeArguments = 5;
 static constexpr dart::compiler::target::word
     SubtypeTestCache_kInstanceClassIdOrFunction = 1;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 6;
+    SubtypeTestCache_kDestinationType = 2;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 5;
+    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 7;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceTypeArguments = 2;
+    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 6;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstantiatorTypeArguments = 3;
+    SubtypeTestCache_kInstanceTypeArguments = 3;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kTestEntryLength = 7;
+    SubtypeTestCache_kInstantiatorTypeArguments = 4;
+static constexpr dart::compiler::target::word
+    SubtypeTestCache_kTestEntryLength = 8;
 static constexpr dart::compiler::target::word SubtypeTestCache_kTestResult = 0;
 static constexpr dart::compiler::target::word TypeArguments_kMaxElements =
     268435455;
@@ -1588,19 +1594,21 @@
 static constexpr dart::compiler::target::word String_kMaxElements =
     2305843009213693951;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kFunctionTypeArguments = 4;
+    SubtypeTestCache_kFunctionTypeArguments = 5;
 static constexpr dart::compiler::target::word
     SubtypeTestCache_kInstanceClassIdOrFunction = 1;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 6;
+    SubtypeTestCache_kDestinationType = 2;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 5;
+    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 7;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceTypeArguments = 2;
+    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 6;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstantiatorTypeArguments = 3;
+    SubtypeTestCache_kInstanceTypeArguments = 3;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kTestEntryLength = 7;
+    SubtypeTestCache_kInstantiatorTypeArguments = 4;
+static constexpr dart::compiler::target::word
+    SubtypeTestCache_kTestEntryLength = 8;
 static constexpr dart::compiler::target::word SubtypeTestCache_kTestResult = 0;
 static constexpr dart::compiler::target::word TypeArguments_kMaxElements =
     576460752303423487;
@@ -2109,19 +2117,21 @@
     NativeEntry_kNumCallWrapperArguments = 2;
 static constexpr dart::compiler::target::word String_kMaxElements = 536870911;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kFunctionTypeArguments = 4;
+    SubtypeTestCache_kFunctionTypeArguments = 5;
 static constexpr dart::compiler::target::word
     SubtypeTestCache_kInstanceClassIdOrFunction = 1;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 6;
+    SubtypeTestCache_kDestinationType = 2;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 5;
+    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 7;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceTypeArguments = 2;
+    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 6;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstantiatorTypeArguments = 3;
+    SubtypeTestCache_kInstanceTypeArguments = 3;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kTestEntryLength = 7;
+    SubtypeTestCache_kInstantiatorTypeArguments = 4;
+static constexpr dart::compiler::target::word
+    SubtypeTestCache_kTestEntryLength = 8;
 static constexpr dart::compiler::target::word SubtypeTestCache_kTestResult = 0;
 static constexpr dart::compiler::target::word TypeArguments_kMaxElements =
     268435455;
@@ -2617,19 +2627,21 @@
 static constexpr dart::compiler::target::word String_kMaxElements =
     2305843009213693951;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kFunctionTypeArguments = 4;
+    SubtypeTestCache_kFunctionTypeArguments = 5;
 static constexpr dart::compiler::target::word
     SubtypeTestCache_kInstanceClassIdOrFunction = 1;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 6;
+    SubtypeTestCache_kDestinationType = 2;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 5;
+    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 7;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceTypeArguments = 2;
+    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 6;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstantiatorTypeArguments = 3;
+    SubtypeTestCache_kInstanceTypeArguments = 3;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kTestEntryLength = 7;
+    SubtypeTestCache_kInstantiatorTypeArguments = 4;
+static constexpr dart::compiler::target::word
+    SubtypeTestCache_kTestEntryLength = 8;
 static constexpr dart::compiler::target::word SubtypeTestCache_kTestResult = 0;
 static constexpr dart::compiler::target::word TypeArguments_kMaxElements =
     576460752303423487;
@@ -3129,19 +3141,21 @@
     NativeEntry_kNumCallWrapperArguments = 2;
 static constexpr dart::compiler::target::word String_kMaxElements = 536870911;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kFunctionTypeArguments = 4;
+    SubtypeTestCache_kFunctionTypeArguments = 5;
 static constexpr dart::compiler::target::word
     SubtypeTestCache_kInstanceClassIdOrFunction = 1;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 6;
+    SubtypeTestCache_kDestinationType = 2;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 5;
+    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 7;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceTypeArguments = 2;
+    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 6;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstantiatorTypeArguments = 3;
+    SubtypeTestCache_kInstanceTypeArguments = 3;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kTestEntryLength = 7;
+    SubtypeTestCache_kInstantiatorTypeArguments = 4;
+static constexpr dart::compiler::target::word
+    SubtypeTestCache_kTestEntryLength = 8;
 static constexpr dart::compiler::target::word SubtypeTestCache_kTestResult = 0;
 static constexpr dart::compiler::target::word TypeArguments_kMaxElements =
     268435455;
@@ -3634,19 +3648,21 @@
 static constexpr dart::compiler::target::word String_kMaxElements =
     2305843009213693951;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kFunctionTypeArguments = 4;
+    SubtypeTestCache_kFunctionTypeArguments = 5;
 static constexpr dart::compiler::target::word
     SubtypeTestCache_kInstanceClassIdOrFunction = 1;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 6;
+    SubtypeTestCache_kDestinationType = 2;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 5;
+    SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 7;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstanceTypeArguments = 2;
+    SubtypeTestCache_kInstanceParentFunctionTypeArguments = 6;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kInstantiatorTypeArguments = 3;
+    SubtypeTestCache_kInstanceTypeArguments = 3;
 static constexpr dart::compiler::target::word
-    SubtypeTestCache_kTestEntryLength = 7;
+    SubtypeTestCache_kInstantiatorTypeArguments = 4;
+static constexpr dart::compiler::target::word
+    SubtypeTestCache_kTestEntryLength = 8;
 static constexpr dart::compiler::target::word SubtypeTestCache_kTestResult = 0;
 static constexpr dart::compiler::target::word TypeArguments_kMaxElements =
     576460752303423487;
@@ -4149,19 +4165,21 @@
 static constexpr dart::compiler::target::word AOT_String_kMaxElements =
     536870911;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kFunctionTypeArguments = 4;
+    AOT_SubtypeTestCache_kFunctionTypeArguments = 5;
 static constexpr dart::compiler::target::word
     AOT_SubtypeTestCache_kInstanceClassIdOrFunction = 1;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 6;
+    AOT_SubtypeTestCache_kDestinationType = 2;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceParentFunctionTypeArguments = 5;
+    AOT_SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 7;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceTypeArguments = 2;
+    AOT_SubtypeTestCache_kInstanceParentFunctionTypeArguments = 6;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstantiatorTypeArguments = 3;
+    AOT_SubtypeTestCache_kInstanceTypeArguments = 3;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kTestEntryLength = 7;
+    AOT_SubtypeTestCache_kInstantiatorTypeArguments = 4;
+static constexpr dart::compiler::target::word
+    AOT_SubtypeTestCache_kTestEntryLength = 8;
 static constexpr dart::compiler::target::word AOT_SubtypeTestCache_kTestResult =
     0;
 static constexpr dart::compiler::target::word AOT_TypeArguments_kMaxElements =
@@ -4719,19 +4737,21 @@
 static constexpr dart::compiler::target::word AOT_String_kMaxElements =
     2305843009213693951;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kFunctionTypeArguments = 4;
+    AOT_SubtypeTestCache_kFunctionTypeArguments = 5;
 static constexpr dart::compiler::target::word
     AOT_SubtypeTestCache_kInstanceClassIdOrFunction = 1;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 6;
+    AOT_SubtypeTestCache_kDestinationType = 2;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceParentFunctionTypeArguments = 5;
+    AOT_SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 7;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceTypeArguments = 2;
+    AOT_SubtypeTestCache_kInstanceParentFunctionTypeArguments = 6;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstantiatorTypeArguments = 3;
+    AOT_SubtypeTestCache_kInstanceTypeArguments = 3;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kTestEntryLength = 7;
+    AOT_SubtypeTestCache_kInstantiatorTypeArguments = 4;
+static constexpr dart::compiler::target::word
+    AOT_SubtypeTestCache_kTestEntryLength = 8;
 static constexpr dart::compiler::target::word AOT_SubtypeTestCache_kTestResult =
     0;
 static constexpr dart::compiler::target::word AOT_TypeArguments_kMaxElements =
@@ -5294,19 +5314,21 @@
 static constexpr dart::compiler::target::word AOT_String_kMaxElements =
     2305843009213693951;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kFunctionTypeArguments = 4;
+    AOT_SubtypeTestCache_kFunctionTypeArguments = 5;
 static constexpr dart::compiler::target::word
     AOT_SubtypeTestCache_kInstanceClassIdOrFunction = 1;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 6;
+    AOT_SubtypeTestCache_kDestinationType = 2;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceParentFunctionTypeArguments = 5;
+    AOT_SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 7;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceTypeArguments = 2;
+    AOT_SubtypeTestCache_kInstanceParentFunctionTypeArguments = 6;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstantiatorTypeArguments = 3;
+    AOT_SubtypeTestCache_kInstanceTypeArguments = 3;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kTestEntryLength = 7;
+    AOT_SubtypeTestCache_kInstantiatorTypeArguments = 4;
+static constexpr dart::compiler::target::word
+    AOT_SubtypeTestCache_kTestEntryLength = 8;
 static constexpr dart::compiler::target::word AOT_SubtypeTestCache_kTestResult =
     0;
 static constexpr dart::compiler::target::word AOT_TypeArguments_kMaxElements =
@@ -5868,19 +5890,21 @@
 static constexpr dart::compiler::target::word AOT_String_kMaxElements =
     536870911;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kFunctionTypeArguments = 4;
+    AOT_SubtypeTestCache_kFunctionTypeArguments = 5;
 static constexpr dart::compiler::target::word
     AOT_SubtypeTestCache_kInstanceClassIdOrFunction = 1;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 6;
+    AOT_SubtypeTestCache_kDestinationType = 2;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceParentFunctionTypeArguments = 5;
+    AOT_SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 7;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceTypeArguments = 2;
+    AOT_SubtypeTestCache_kInstanceParentFunctionTypeArguments = 6;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstantiatorTypeArguments = 3;
+    AOT_SubtypeTestCache_kInstanceTypeArguments = 3;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kTestEntryLength = 7;
+    AOT_SubtypeTestCache_kInstantiatorTypeArguments = 4;
+static constexpr dart::compiler::target::word
+    AOT_SubtypeTestCache_kTestEntryLength = 8;
 static constexpr dart::compiler::target::word AOT_SubtypeTestCache_kTestResult =
     0;
 static constexpr dart::compiler::target::word AOT_TypeArguments_kMaxElements =
@@ -6431,19 +6455,21 @@
 static constexpr dart::compiler::target::word AOT_String_kMaxElements =
     2305843009213693951;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kFunctionTypeArguments = 4;
+    AOT_SubtypeTestCache_kFunctionTypeArguments = 5;
 static constexpr dart::compiler::target::word
     AOT_SubtypeTestCache_kInstanceClassIdOrFunction = 1;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 6;
+    AOT_SubtypeTestCache_kDestinationType = 2;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceParentFunctionTypeArguments = 5;
+    AOT_SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 7;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceTypeArguments = 2;
+    AOT_SubtypeTestCache_kInstanceParentFunctionTypeArguments = 6;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstantiatorTypeArguments = 3;
+    AOT_SubtypeTestCache_kInstanceTypeArguments = 3;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kTestEntryLength = 7;
+    AOT_SubtypeTestCache_kInstantiatorTypeArguments = 4;
+static constexpr dart::compiler::target::word
+    AOT_SubtypeTestCache_kTestEntryLength = 8;
 static constexpr dart::compiler::target::word AOT_SubtypeTestCache_kTestResult =
     0;
 static constexpr dart::compiler::target::word AOT_TypeArguments_kMaxElements =
@@ -6999,19 +7025,21 @@
 static constexpr dart::compiler::target::word AOT_String_kMaxElements =
     2305843009213693951;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kFunctionTypeArguments = 4;
+    AOT_SubtypeTestCache_kFunctionTypeArguments = 5;
 static constexpr dart::compiler::target::word
     AOT_SubtypeTestCache_kInstanceClassIdOrFunction = 1;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 6;
+    AOT_SubtypeTestCache_kDestinationType = 2;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceParentFunctionTypeArguments = 5;
+    AOT_SubtypeTestCache_kInstanceDelayedFunctionTypeArguments = 7;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstanceTypeArguments = 2;
+    AOT_SubtypeTestCache_kInstanceParentFunctionTypeArguments = 6;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kInstantiatorTypeArguments = 3;
+    AOT_SubtypeTestCache_kInstanceTypeArguments = 3;
 static constexpr dart::compiler::target::word
-    AOT_SubtypeTestCache_kTestEntryLength = 7;
+    AOT_SubtypeTestCache_kInstantiatorTypeArguments = 4;
+static constexpr dart::compiler::target::word
+    AOT_SubtypeTestCache_kTestEntryLength = 8;
 static constexpr dart::compiler::target::word AOT_SubtypeTestCache_kTestResult =
     0;
 static constexpr dart::compiler::target::word AOT_TypeArguments_kMaxElements =
diff --git a/runtime/vm/compiler/runtime_offsets_list.h b/runtime/vm/compiler/runtime_offsets_list.h
index f54a380..98ab25f 100644
--- a/runtime/vm/compiler/runtime_offsets_list.h
+++ b/runtime/vm/compiler/runtime_offsets_list.h
@@ -54,6 +54,7 @@
   CONSTANT(String, kMaxElements)                                               \
   CONSTANT(SubtypeTestCache, kFunctionTypeArguments)                           \
   CONSTANT(SubtypeTestCache, kInstanceClassIdOrFunction)                       \
+  CONSTANT(SubtypeTestCache, kDestinationType)                                 \
   CONSTANT(SubtypeTestCache, kInstanceDelayedFunctionTypeArguments)            \
   CONSTANT(SubtypeTestCache, kInstanceParentFunctionTypeArguments)             \
   CONSTANT(SubtypeTestCache, kInstanceTypeArguments)                           \
diff --git a/runtime/vm/compiler/stub_code_compiler.cc b/runtime/vm/compiler/stub_code_compiler.cc
index ede86e0..7536df5 100644
--- a/runtime/vm/compiler/stub_code_compiler.cc
+++ b/runtime/vm/compiler/stub_code_compiler.cc
@@ -381,7 +381,7 @@
   __ Bind(&is_simple_case);
   {
     __ PushRegisters(caller_saved_registers);
-    __ Call(StubCodeSubtype2TestCache());
+    __ Call(StubCodeSubtype3TestCache());
     __ CompareObject(TypeTestABI::kSubtypeTestCacheResultReg,
                      CastHandle<Object>(TrueObject()));
     __ PopRegisters(caller_saved_registers);
@@ -392,7 +392,7 @@
   __ Bind(&is_complex_case);
   {
     __ PushRegisters(caller_saved_registers);
-    __ Call(StubCodeSubtype6TestCache());
+    __ Call(StubCodeSubtype7TestCache());
     __ CompareObject(TypeTestABI::kSubtypeTestCacheResultReg,
                      CastHandle<Object>(TrueObject()));
     __ PopRegisters(caller_saved_registers);
diff --git a/runtime/vm/compiler/stub_code_compiler_arm.cc b/runtime/vm/compiler/stub_code_compiler_arm.cc
index 995fa9e..4bc6aee 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm.cc
@@ -2611,29 +2611,32 @@
 //
 // Inputs (mostly from TypeTestABI struct):
 //   - kSubtypeTestCacheReg: SubtypeTestCacheLayout
-//   - kInstanceReg: instance to test against (must be preserved).
-//   - kInstantiatorTypeArgumentsReg: instantiator type arguments (for n=4).
-//   - kFunctionTypeArgumentsReg: function type arguments (for n=4).
+//   - kInstanceReg: instance to test against.
+//   - kDstTypeReg: destination type (for n>=3).
+//   - kInstantiatorTypeArgumentsReg: instantiator type arguments (for n=5).
+//   - kFunctionTypeArgumentsReg: function type arguments (for n=5).
 //   - LR: return address.
 //
-// All TypeTestABI registers are preserved but kSubtypeTestCacheReg and
-// kDstTypeReg, which must be saved by the caller if the original value is
-// needed after the call.
+// All TypeTestABI registers are preserved but kSubtypeTestCacheReg, which must
+// be saved by the caller if the original value is needed after the call.
 //
-// Result in SubtypeTestCacheReg::kResultReg: null -> not found, otherwise
+// Result in SubtypeTestCacheABI::kResultReg: null -> not found, otherwise
 // result (true or false).
 static void GenerateSubtypeNTestCacheStub(Assembler* assembler, int n) {
-  ASSERT(n == 1 || n == 2 || n == 4 || n == 6);
+  ASSERT(n == 1 || n == 3 || n == 5 || n == 7);
 
   // Safe as the original value of TypeTestABI::kSubtypeTestCacheReg is only
   // used to initialize this register.
   const Register kCacheArrayReg = TypeTestABI::kSubtypeTestCacheReg;
   const Register kScratchReg = TypeTestABI::kScratchReg;
-  const Register kInstanceCidOrFunction = TypeTestABI::kDstTypeReg;
-  const Register kInstanceInstantiatorTypeArgumentsReg = R9;
-  // Registers that are only used for n >= 6 and must be preserved if used.
+  const Register kInstanceCidOrFunction = R9;
+  // Registers that are only used for n >= 3 and must be preserved if used.
+  Register kInstanceInstantiatorTypeArgumentsReg = kNoRegister;
+  // Registers that are only used for n >= 7 and must be preserved if used.
   Register kInstanceParentFunctionTypeArgumentsReg = kNoRegister;
-  Register kInstanceDelayedFunctionTypeArgumentsReg = kNoRegister;
+  // There is no register for InstanceDelayedFunctionTypeArguments, it is
+  // instead placed on the stack and pulled into TMP for comparison against
+  // the corresponding slot in the current cache entry.
 
   // NOTFP must be preserved for bare payloads, otherwise CODE_REG.
   const bool use_bare_payloads =
@@ -2642,15 +2645,17 @@
   const Register kNullReg = use_bare_payloads ? CODE_REG : NOTFP;
   __ LoadObject(kNullReg, NullObject());
 
+  // Free up registers to be used if performing a 3, 5, or 7 value test.
   RegList pushed_registers = 0;
-  // Free up these 2 registers to be used for 6-value test.
-  if (n >= 6) {
+  if (n >= 3) {
+    kInstanceInstantiatorTypeArgumentsReg = PP;
+    pushed_registers |= 1 << kInstanceInstantiatorTypeArgumentsReg;
+  }
+  if (n >= 7) {
     // For this, we choose the register that must be preserved of the pair.
     kInstanceParentFunctionTypeArgumentsReg =
         use_bare_payloads ? NOTFP : CODE_REG;
-    kInstanceDelayedFunctionTypeArgumentsReg = PP;
-    pushed_registers |= 1 << kInstanceParentFunctionTypeArgumentsReg |
-                        1 << kInstanceDelayedFunctionTypeArgumentsReg;
+    pushed_registers |= 1 << kInstanceParentFunctionTypeArgumentsReg;
   }
   if (pushed_registers != 0) {
     __ PushList(pushed_registers);
@@ -2668,7 +2673,7 @@
                   target::Array::data_offset() - kHeapObjectTag);
 
   Label loop, not_closure;
-  if (n >= 4) {
+  if (n >= 5) {
     __ LoadClassIdMayBeSmi(kInstanceCidOrFunction, TypeTestABI::kInstanceReg);
   } else {
     __ LoadClassId(kInstanceCidOrFunction, TypeTestABI::kInstanceReg);
@@ -2681,19 +2686,19 @@
     __ ldr(kInstanceCidOrFunction,
            FieldAddress(TypeTestABI::kInstanceReg,
                         target::Closure::function_offset()));
-    if (n >= 2) {
+    if (n >= 3) {
       __ ldr(
           kInstanceInstantiatorTypeArgumentsReg,
           FieldAddress(TypeTestABI::kInstanceReg,
                        target::Closure::instantiator_type_arguments_offset()));
-      if (n >= 6) {
-        ASSERT(n == 6);
+      if (n >= 7) {
         __ ldr(kInstanceParentFunctionTypeArgumentsReg,
                FieldAddress(TypeTestABI::kInstanceReg,
                             target::Closure::function_type_arguments_offset()));
-        __ ldr(kInstanceDelayedFunctionTypeArgumentsReg,
+        __ ldr(kScratchReg,
                FieldAddress(TypeTestABI::kInstanceReg,
                             target::Closure::delayed_type_arguments_offset()));
+        __ PushRegister(kScratchReg);
       }
     }
     __ b(&loop);
@@ -2702,7 +2707,7 @@
   // Non-Closure handling.
   {
     __ Bind(&not_closure);
-    if (n >= 2) {
+    if (n >= 3) {
       Label has_no_type_arguments;
       __ LoadClassById(kScratchReg, kInstanceCidOrFunction);
       __ mov(kInstanceInstantiatorTypeArgumentsReg, Operand(kNullReg));
@@ -2719,14 +2724,18 @@
              FieldAddress(kScratchReg, 0));
       __ Bind(&has_no_type_arguments);
 
-      if (n >= 6) {
+      if (n >= 7) {
         __ mov(kInstanceParentFunctionTypeArgumentsReg, Operand(kNullReg));
-        __ mov(kInstanceDelayedFunctionTypeArgumentsReg, Operand(kNullReg));
+        __ PushRegister(kNullReg);
       }
     }
     __ SmiTag(kInstanceCidOrFunction);
   }
 
+  const intptr_t kNoDepth = -1;
+  const intptr_t kInstanceDelayedFunctionTypeArgumentsDepth =
+      n >= 7 ? 0 : kNoDepth;
+
   Label found, not_found, next_iteration;
 
   // Loop header.
@@ -2743,11 +2752,17 @@
   } else {
     __ b(&next_iteration, NE);
     __ ldr(kScratchReg,
+           Address(
+               kCacheArrayReg,
+               target::kWordSize * target::SubtypeTestCache::kDestinationType));
+    __ cmp(kScratchReg, Operand(TypeTestABI::kDstTypeReg));
+    __ b(&next_iteration, NE);
+    __ ldr(kScratchReg,
            Address(kCacheArrayReg,
                    target::kWordSize *
                        target::SubtypeTestCache::kInstanceTypeArguments));
     __ cmp(kScratchReg, Operand(kInstanceInstantiatorTypeArgumentsReg));
-    if (n == 2) {
+    if (n == 3) {
       __ b(&found, EQ);
     } else {
       __ b(&next_iteration, NE);
@@ -2762,10 +2777,10 @@
                      target::kWordSize *
                          target::SubtypeTestCache::kFunctionTypeArguments));
       __ cmp(kScratchReg, Operand(TypeTestABI::kFunctionTypeArgumentsReg));
-      if (n == 4) {
+      if (n == 5) {
         __ b(&found, EQ);
       } else {
-        ASSERT(n == 6);
+        ASSERT(n == 7);
         __ b(&next_iteration, NE);
 
         __ ldr(kScratchReg,
@@ -2781,7 +2796,8 @@
                        target::kWordSize *
                            target::SubtypeTestCache::
                                kInstanceDelayedFunctionTypeArguments));
-        __ cmp(kScratchReg, Operand(kInstanceDelayedFunctionTypeArgumentsReg));
+        __ CompareToStack(kScratchReg,
+                          kInstanceDelayedFunctionTypeArgumentsDepth);
         __ b(&found, EQ);
       }
     }
@@ -2796,6 +2812,9 @@
   __ ldr(TypeTestABI::kSubtypeTestCacheResultReg,
          Address(kCacheArrayReg,
                  target::kWordSize * target::SubtypeTestCache::kTestResult));
+  if (n >= 7) {
+    __ Drop(1);  // delayed function type args.
+  }
   if (pushed_registers != 0) {
     __ PopList(pushed_registers);
   }
@@ -2803,6 +2822,9 @@
 
   __ Bind(&not_found);
   __ mov(TypeTestABI::kSubtypeTestCacheResultReg, Operand(kNullReg));
+  if (n >= 7) {
+    __ Drop(1);  // delayed function type args.
+  }
   if (pushed_registers != 0) {
     __ PopList(pushed_registers);
   }
@@ -2815,18 +2837,18 @@
 }
 
 // See comment on [GenerateSubtypeNTestCacheStub].
-void StubCodeCompiler::GenerateSubtype2TestCacheStub(Assembler* assembler) {
-  GenerateSubtypeNTestCacheStub(assembler, 2);
+void StubCodeCompiler::GenerateSubtype3TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 3);
 }
 
 // See comment on [GenerateSubtypeNTestCacheStub].
-void StubCodeCompiler::GenerateSubtype4TestCacheStub(Assembler* assembler) {
-  GenerateSubtypeNTestCacheStub(assembler, 4);
+void StubCodeCompiler::GenerateSubtype5TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 5);
 }
 
-// See comment on [GenerateSubtypeNTestCacheStub].
-void StubCodeCompiler::GenerateSubtype6TestCacheStub(Assembler* assembler) {
-  GenerateSubtypeNTestCacheStub(assembler, 6);
+// See comment on[GenerateSubtypeNTestCacheStub].
+void StubCodeCompiler::GenerateSubtype7TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 7);
 }
 
 // Return the current stack pointer address, used to do stack alignment checks.
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index f64edbe..1ef8b6a 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -2792,17 +2792,18 @@
 // Inputs (mostly from TypeTestABI struct):
 //   - kSubtypeTestCacheReg: SubtypeTestCacheLayout
 //   - kInstanceReg: instance to test against.
-//   - kInstantiatorTypeArgumentsReg: instantiator type arguments (for n=4).
-//   - kFunctionTypeArgumentsReg: function type arguments (for n=4).
+//   - kDstTypeReg: destination type (for n>=3).
+//   - kInstantiatorTypeArgumentsReg: instantiator type arguments (for n=5).
+//   - kFunctionTypeArgumentsReg: function type arguments (for n=5).
 //   - LR: return address.
 //
 // All input registers are preserved except for kSubtypeTestCacheReg, which
 // should be saved by the caller if needed.
 //
-// Result in SubtypeTestCacheReg::kResultReg: null -> not found, otherwise
+// Result in SubtypeTestCacheABI::kResultReg: null -> not found, otherwise
 // result (true or false).
 static void GenerateSubtypeNTestCacheStub(Assembler* assembler, int n) {
-  ASSERT(n == 1 || n == 2 || n == 4 || n == 6);
+  ASSERT(n == 1 || n == 3 || n == 5 || n == 7);
 
   // Until we have the result, we use the result register to store the null
   // value for quick access. This has the side benefit of initializing the
@@ -2832,7 +2833,7 @@
                   target::Array::data_offset() - kHeapObjectTag);
 
   Label loop, not_closure;
-  if (n >= 4) {
+  if (n >= 5) {
     __ LoadClassIdMayBeSmi(kInstanceCidOrFunction,
                            TypeTestABI::TypeTestABI::kInstanceReg);
   } else {
@@ -2846,13 +2847,12 @@
     __ ldr(kInstanceCidOrFunction,
            FieldAddress(TypeTestABI::kInstanceReg,
                         target::Closure::function_offset()));
-    if (n >= 2) {
+    if (n >= 3) {
       __ ldr(
           kInstanceInstantiatorTypeArgumentsReg,
           FieldAddress(TypeTestABI::kInstanceReg,
                        target::Closure::instantiator_type_arguments_offset()));
-      if (n >= 6) {
-        ASSERT(n == 6);
+      if (n >= 7) {
         __ ldr(kInstanceParentFunctionTypeArgumentsReg,
                FieldAddress(TypeTestABI::kInstanceReg,
                             target::Closure::function_type_arguments_offset()));
@@ -2867,7 +2867,7 @@
   // Non-Closure handling.
   {
     __ Bind(&not_closure);
-    if (n >= 2) {
+    if (n >= 3) {
       Label has_no_type_arguments;
       __ LoadClassById(kScratchReg, kInstanceCidOrFunction);
       __ mov(kInstanceInstantiatorTypeArgumentsReg, kNullReg);
@@ -2883,7 +2883,7 @@
              FieldAddress(kScratchReg, 0));
       __ Bind(&has_no_type_arguments);
 
-      if (n >= 6) {
+      if (n >= 7) {
         __ mov(kInstanceParentFunctionTypeArgumentsReg, kNullReg);
         __ mov(kInstanceDelayedFunctionTypeArgumentsReg, kNullReg);
       }
@@ -2907,11 +2907,17 @@
   } else {
     __ b(&next_iteration, NE);
     __ ldr(kScratchReg,
+           Address(
+               kCacheArrayReg,
+               target::kWordSize * target::SubtypeTestCache::kDestinationType));
+    __ cmp(kScratchReg, Operand(TypeTestABI::kDstTypeReg));
+    __ b(&next_iteration, NE);
+    __ ldr(kScratchReg,
            Address(kCacheArrayReg,
                    target::kWordSize *
                        target::SubtypeTestCache::kInstanceTypeArguments));
     __ cmp(kScratchReg, Operand(kInstanceInstantiatorTypeArgumentsReg));
-    if (n == 2) {
+    if (n == 3) {
       __ b(&found, EQ);
     } else {
       __ b(&next_iteration, NE);
@@ -2926,10 +2932,10 @@
                      target::kWordSize *
                          target::SubtypeTestCache::kFunctionTypeArguments));
       __ cmp(kScratchReg, Operand(TypeTestABI::kFunctionTypeArgumentsReg));
-      if (n == 4) {
+      if (n == 5) {
         __ b(&found, EQ);
       } else {
-        ASSERT(n == 6);
+        ASSERT(n == 7);
         __ b(&next_iteration, NE);
 
         __ ldr(kScratchReg,
@@ -2970,18 +2976,18 @@
 }
 
 // See comment on [GenerateSubtypeNTestCacheStub].
-void StubCodeCompiler::GenerateSubtype2TestCacheStub(Assembler* assembler) {
-  GenerateSubtypeNTestCacheStub(assembler, 2);
+void StubCodeCompiler::GenerateSubtype3TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 3);
 }
 
 // See comment on [GenerateSubtypeNTestCacheStub].
-void StubCodeCompiler::GenerateSubtype4TestCacheStub(Assembler* assembler) {
-  GenerateSubtypeNTestCacheStub(assembler, 4);
+void StubCodeCompiler::GenerateSubtype5TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 5);
 }
 
 // See comment on [GenerateSubtypeNTestCacheStub].
-void StubCodeCompiler::GenerateSubtype6TestCacheStub(Assembler* assembler) {
-  GenerateSubtypeNTestCacheStub(assembler, 6);
+void StubCodeCompiler::GenerateSubtype7TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 7);
 }
 
 void StubCodeCompiler::GenerateGetCStackPointerStub(Assembler* assembler) {
diff --git a/runtime/vm/compiler/stub_code_compiler_ia32.cc b/runtime/vm/compiler/stub_code_compiler_ia32.cc
index b306279..17f0456 100644
--- a/runtime/vm/compiler/stub_code_compiler_ia32.cc
+++ b/runtime/vm/compiler/stub_code_compiler_ia32.cc
@@ -2184,15 +2184,16 @@
 // TOS + 0: return address.
 // TOS + 1: function type arguments (only if n == 4, can be raw_null).
 // TOS + 2: instantiator type arguments (only if n == 4, can be raw_null).
-// TOS + 3: instance.
-// TOS + 4: SubtypeTestCache.
+// TOS + 3: destination_type (only used if n >= 3).
+// TOS + 4: instance.
+// TOS + 5: SubtypeTestCache.
 //
 // No registers are preserved by this stub.
 //
 // Result in SubtypeTestCacheReg::kResultReg: null -> not found, otherwise
 // result (true or false).
 static void GenerateSubtypeNTestCacheStub(Assembler* assembler, int n) {
-  ASSERT(n == 1 || n == 2 || n == 4 || n == 6);
+  ASSERT(n == 1 || n == 3 || n == 5 || n == 7);
 
   // We represent the depth of as a depth from the top of the stack at the
   // start of the stub. That is, depths for input values are non-negative and
@@ -2206,8 +2207,9 @@
   // Inputs use relative depths.
   static constexpr intptr_t kFunctionTypeArgumentsDepth = 1;
   static constexpr intptr_t kInstantiatorTypeArgumentsDepth = 2;
-  static constexpr intptr_t kInstanceDepth = 3;
-  static constexpr intptr_t kCacheDepth = 4;
+  static constexpr intptr_t kDestinationTypeDepth = 3;
+  static constexpr intptr_t kInstanceDepth = 4;
+  static constexpr intptr_t kCacheDepth = 5;
   // Others use absolute depths. We initialize conditionally pushed values to
   // kNoInput for extra checking.
   intptr_t kInstanceParentFunctionTypeArgumentsDepth = kNoDepth;
@@ -2224,15 +2226,13 @@
   // Loads a value at the given depth from the stack into dst.
   auto load_from_stack = [&](Register dst, intptr_t depth) {
     ASSERT(depth != kNoDepth);
-    __ movl(dst,
-            Address(ESP, (original_tos_offset + depth) * target::kWordSize));
+    __ LoadFromStack(dst, original_tos_offset + depth);
   };
 
   // Compares a value at the given depth from the stack to the value in src.
   auto compare_to_stack = [&](Register src, intptr_t depth) {
     ASSERT(depth != kNoDepth);
-    __ cmpl(src,
-            Address(ESP, (original_tos_offset + depth) * target::kWordSize));
+    __ CompareToStack(src, original_tos_offset + depth);
   };
 
   const auto& raw_null = Immediate(target::ToRawPointer(NullObject()));
@@ -2251,7 +2251,7 @@
           Immediate(target::Array::data_offset() - kHeapObjectTag));
 
   Label loop, not_closure;
-  if (n >= 4) {
+  if (n >= 5) {
     __ LoadClassIdMayBeSmi(kInstanceCidOrFunction, TypeTestABI::kInstanceReg);
   } else {
     __ LoadClassId(kInstanceCidOrFunction, TypeTestABI::kInstanceReg);
@@ -2264,12 +2264,12 @@
     __ movl(kInstanceCidOrFunction,
             FieldAddress(TypeTestABI::kInstanceReg,
                          target::Closure::function_offset()));
-    if (n >= 2) {
+    if (n >= 3) {
       __ movl(
           kInstanceInstantiatorTypeArgumentsReg,
           FieldAddress(TypeTestABI::kInstanceReg,
                        target::Closure::instantiator_type_arguments_offset()));
-      if (n >= 6) {
+      if (n >= 7) {
         __ pushl(
             FieldAddress(TypeTestABI::kInstanceReg,
                          target::Closure::delayed_type_arguments_offset()));
@@ -2284,7 +2284,7 @@
   // Non-Closure handling.
   {
     __ Bind(&not_closure);
-    if (n >= 2) {
+    if (n >= 3) {
       Label has_no_type_arguments;
       __ LoadClassById(kScratchReg, kInstanceCidOrFunction);
       __ movl(kInstanceInstantiatorTypeArgumentsReg, raw_null);
@@ -2299,7 +2299,7 @@
               FieldAddress(TypeTestABI::kInstanceReg, kScratchReg, TIMES_4, 0));
       __ Bind(&has_no_type_arguments);
 
-      if (n >= 6) {
+      if (n >= 7) {
         __ pushl(raw_null);  // delayed.
         __ pushl(raw_null);  // function.
       }
@@ -2307,7 +2307,7 @@
     __ SmiTag(kInstanceCidOrFunction);
   }
 
-  if (n >= 6) {
+  if (n >= 7) {
     // Now that instance handling is done, both the delayed and parent function
     // type arguments stack slots have been set, so any input uses must be
     // offset by the new values and the new values can now be accessed in
@@ -2332,11 +2332,17 @@
     __ j(EQUAL, &done, Assembler::kNearJump);
   } else {
     __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
+    __ movl(kScratchReg,
+            Address(kCacheArrayReg,
+                    target::kWordSize *
+                        target::SubtypeTestCache::kDestinationType));
+    compare_to_stack(kScratchReg, kDestinationTypeDepth);
+    __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
     __ cmpl(kInstanceInstantiatorTypeArgumentsReg,
             Address(kCacheArrayReg,
                     target::kWordSize *
                         target::SubtypeTestCache::kInstanceTypeArguments));
-    if (n == 2) {
+    if (n == 3) {
       __ j(EQUAL, &done, Assembler::kNearJump);
     } else {
       __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
@@ -2352,10 +2358,10 @@
                       target::kWordSize *
                           target::SubtypeTestCache::kFunctionTypeArguments));
       compare_to_stack(kScratchReg, kFunctionTypeArgumentsDepth);
-      if (n == 4) {
+      if (n == 5) {
         __ j(EQUAL, &done, Assembler::kNearJump);
       } else {
-        ASSERT(n == 6);
+        ASSERT(n == 7);
         __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
 
         __ movl(kScratchReg,
@@ -2389,7 +2395,7 @@
   __ movl(TypeTestABI::kSubtypeTestCacheResultReg,
           Address(kCacheArrayReg,
                   target::kWordSize * target::SubtypeTestCache::kTestResult));
-  if (n >= 6) {
+  if (n >= 7) {
     __ Drop(2);
     original_tos_offset = 0;  // In case we add any input uses after this point.
   }
@@ -2402,18 +2408,18 @@
 }
 
 // See comment on [GenerateSubtypeNTestCacheStub].
-void StubCodeCompiler::GenerateSubtype2TestCacheStub(Assembler* assembler) {
-  GenerateSubtypeNTestCacheStub(assembler, 2);
+void StubCodeCompiler::GenerateSubtype3TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 3);
 }
 
 // See comment on [GenerateSubtypeNTestCacheStub].
-void StubCodeCompiler::GenerateSubtype4TestCacheStub(Assembler* assembler) {
-  GenerateSubtypeNTestCacheStub(assembler, 4);
+void StubCodeCompiler::GenerateSubtype5TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 5);
 }
 
 // See comment on [GenerateSubtypeNTestCacheStub].
-void StubCodeCompiler::GenerateSubtype6TestCacheStub(Assembler* assembler) {
-  GenerateSubtypeNTestCacheStub(assembler, 6);
+void StubCodeCompiler::GenerateSubtype7TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 7);
 }
 
 // Return the current stack pointer address, used to do stack alignment checks.
diff --git a/runtime/vm/compiler/stub_code_compiler_x64.cc b/runtime/vm/compiler/stub_code_compiler_x64.cc
index 17d7303..1b6ef0b 100644
--- a/runtime/vm/compiler/stub_code_compiler_x64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_x64.cc
@@ -2737,8 +2737,9 @@
 // Input registers (from TypeTestABI struct):
 //   - kSubtypeTestCacheReg: SubtypeTestCacheLayout
 //   - kInstanceReg: instance to test against (must be preserved).
-//   - kInstantiatorTypeArgumentsReg: instantiator type arguments (for n>=4).
-//   - kFunctionTypeArgumentsReg: function type arguments (for n>=4).
+//   - kDstTypeReg: destination type (for n>=3).
+//   - kInstantiatorTypeArgumentsReg : instantiator type arguments (for n>=5).
+//   - kFunctionTypeArgumentsReg : function type arguments (for n>=5).
 // Inputs from stack:
 //   - TOS + 0: return address.
 //
@@ -2747,7 +2748,7 @@
 // Result in SubtypeTestCacheReg::kResultReg: null -> not found, otherwise
 // result (true or false).
 static void GenerateSubtypeNTestCacheStub(Assembler* assembler, int n) {
-  ASSERT(n == 1 || n == 2 || n == 4 || n == 6);
+  ASSERT(n == 1 || n == 3 || n == 5 || n == 7);
 
   // Until we have the result, we use the result register to store the null
   // value for quick access. This has the side benefit of initializing the
@@ -2761,12 +2762,12 @@
   const Register kScratchReg = TypeTestABI::kScratchReg;
   const Register kInstanceCidOrFunction = R10;
   const Register kInstanceInstantiatorTypeArgumentsReg = R13;
-  // Only used for n >= 6, so set conditionally in that case to catch misuse.
+  // Only used for n >= 7, so set conditionally in that case to catch misuse.
   Register kInstanceParentFunctionTypeArgumentsReg = kNoRegister;
   Register kInstanceDelayedFunctionTypeArgumentsReg = kNoRegister;
 
-  // Free up these 2 registers to be used for 6-value test.
-  if (n >= 6) {
+  // Free up these 2 registers to be used for 7-value test.
+  if (n >= 7) {
     kInstanceParentFunctionTypeArgumentsReg = PP;
     kInstanceDelayedFunctionTypeArgumentsReg = CODE_REG;
     __ pushq(kInstanceParentFunctionTypeArgumentsReg);
@@ -2785,7 +2786,7 @@
           Immediate(target::Array::data_offset() - kHeapObjectTag));
 
   Label loop, not_closure;
-  if (n >= 4) {
+  if (n >= 5) {
     __ LoadClassIdMayBeSmi(kInstanceCidOrFunction, TypeTestABI::kInstanceReg);
   } else {
     __ LoadClassId(kInstanceCidOrFunction, TypeTestABI::kInstanceReg);
@@ -2798,13 +2799,12 @@
     __ movq(kInstanceCidOrFunction,
             FieldAddress(TypeTestABI::kInstanceReg,
                          target::Closure::function_offset()));
-    if (n >= 2) {
+    if (n >= 3) {
       __ movq(
           kInstanceInstantiatorTypeArgumentsReg,
           FieldAddress(TypeTestABI::kInstanceReg,
                        target::Closure::instantiator_type_arguments_offset()));
-      if (n >= 6) {
-        ASSERT(n == 6);
+      if (n >= 7) {
         __ movq(
             kInstanceParentFunctionTypeArgumentsReg,
             FieldAddress(TypeTestABI::kInstanceReg,
@@ -2820,7 +2820,7 @@
   // Non-Closure handling.
   {
     __ Bind(&not_closure);
-    if (n >= 2) {
+    if (n >= 3) {
       Label has_no_type_arguments;
       __ LoadClassById(kScratchReg, kInstanceCidOrFunction);
       __ movq(kInstanceInstantiatorTypeArgumentsReg, kNullReg);
@@ -2835,7 +2835,7 @@
               FieldAddress(TypeTestABI::kInstanceReg, kScratchReg, TIMES_8, 0));
       __ Bind(&has_no_type_arguments);
 
-      if (n >= 6) {
+      if (n >= 7) {
         __ movq(kInstanceParentFunctionTypeArgumentsReg, kNullReg);
         __ movq(kInstanceDelayedFunctionTypeArgumentsReg, kNullReg);
       }
@@ -2858,11 +2858,16 @@
     __ j(EQUAL, &found, Assembler::kNearJump);
   } else {
     __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
+    __ cmpq(TypeTestABI::kDstTypeReg,
+            Address(kCacheArrayReg,
+                    target::kWordSize *
+                        target::SubtypeTestCache::kDestinationType));
+    __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
     __ cmpq(kInstanceInstantiatorTypeArgumentsReg,
             Address(kCacheArrayReg,
                     target::kWordSize *
                         target::SubtypeTestCache::kInstanceTypeArguments));
-    if (n == 2) {
+    if (n == 3) {
       __ j(EQUAL, &found, Assembler::kNearJump);
     } else {
       __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
@@ -2877,10 +2882,10 @@
                       target::kWordSize *
                           target::SubtypeTestCache::kFunctionTypeArguments));
 
-      if (n == 4) {
+      if (n == 5) {
         __ j(EQUAL, &found, Assembler::kNearJump);
       } else {
-        ASSERT(n == 6);
+        ASSERT(n == 7);
         __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
 
         __ cmpq(kInstanceParentFunctionTypeArgumentsReg,
@@ -2911,7 +2916,7 @@
                   target::kWordSize * target::SubtypeTestCache::kTestResult));
 
   __ Bind(&not_found);
-  if (n >= 6) {
+  if (n >= 7) {
     __ popq(kInstanceDelayedFunctionTypeArgumentsReg);
     __ popq(kInstanceParentFunctionTypeArgumentsReg);
   }
@@ -2924,18 +2929,18 @@
 }
 
 // See comment on [GenerateSubtypeNTestCacheStub].
-void StubCodeCompiler::GenerateSubtype2TestCacheStub(Assembler* assembler) {
-  GenerateSubtypeNTestCacheStub(assembler, 2);
+void StubCodeCompiler::GenerateSubtype3TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 3);
 }
 
 // See comment on [GenerateSubtypeNTestCacheStub].
-void StubCodeCompiler::GenerateSubtype4TestCacheStub(Assembler* assembler) {
-  GenerateSubtypeNTestCacheStub(assembler, 4);
+void StubCodeCompiler::GenerateSubtype5TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 5);
 }
 
 // See comment on [GenerateSubtypeNTestCacheStub].
-void StubCodeCompiler::GenerateSubtype6TestCacheStub(Assembler* assembler) {
-  GenerateSubtypeNTestCacheStub(assembler, 6);
+void StubCodeCompiler::GenerateSubtype7TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 7);
 }
 
 // Return the current stack pointer address, used to stack alignment
diff --git a/runtime/vm/constants_arm.h b/runtime/vm/constants_arm.h
index dd2f3a7..82a86da 100644
--- a/runtime/vm/constants_arm.h
+++ b/runtime/vm/constants_arm.h
@@ -341,7 +341,7 @@
 
   // Registers that need saving across SubtypeTestCacheStub calls.
   static const intptr_t kSubtypeTestCacheStubCallerSavedRegisters =
-      (1 << kSubtypeTestCacheReg) | (1 << kDstTypeReg);
+      1 << kSubtypeTestCacheReg;
 
   static const intptr_t kAbiRegisters =
       (1 << kInstanceReg) | (1 << kDstTypeReg) |
diff --git a/runtime/vm/constants_arm64.h b/runtime/vm/constants_arm64.h
index 7b18369..da0e6d7 100644
--- a/runtime/vm/constants_arm64.h
+++ b/runtime/vm/constants_arm64.h
@@ -173,7 +173,7 @@
 
   // Registers that need saving across SubtypeTestCacheStub calls.
   static const intptr_t kSubtypeTestCacheStubCallerSavedRegisters =
-      (1 << kFunctionTypeArgumentsReg) | (1 << kSubtypeTestCacheReg);
+      1 << kSubtypeTestCacheReg;
 
   static const intptr_t kAbiRegisters =
       (1 << kInstanceReg) | (1 << kDstTypeReg) |
diff --git a/runtime/vm/constants_ia32.h b/runtime/vm/constants_ia32.h
index a07e20f..1827e82 100644
--- a/runtime/vm/constants_ia32.h
+++ b/runtime/vm/constants_ia32.h
@@ -118,7 +118,7 @@
       EDI;  // On ia32 we don't use CODE_REG.
 
   // For call to InstanceOfStub.
-  static const Register kInstanceOfResultReg = kNoRegister;
+  static const Register kInstanceOfResultReg = kInstanceReg;
   // For call to SubtypeNTestCacheStub.
   static const Register kSubtypeTestCacheResultReg =
       TypeTestABI::kSubtypeTestCacheReg;
diff --git a/runtime/vm/isolate_reload.cc b/runtime/vm/isolate_reload.cc
index b126b1c..18a32b7 100644
--- a/runtime/vm/isolate_reload.cc
+++ b/runtime/vm/isolate_reload.cc
@@ -2270,6 +2270,8 @@
          i += SubtypeTestCache::kTestEntryLength) {
       if ((entries_.At(i + SubtypeTestCache::kInstanceClassIdOrFunction) ==
            instance_cid_or_function_.raw()) &&
+          (entries_.At(i + SubtypeTestCache::kDestinationType) ==
+           type_.raw()) &&
           (entries_.At(i + SubtypeTestCache::kInstanceTypeArguments) ==
            instance_type_arguments_.raw()) &&
           (entries_.At(i + SubtypeTestCache::kInstantiatorTypeArguments) ==
@@ -2298,8 +2300,9 @@
         ASSERT(!FLAG_identity_reload);
         field.set_needs_load_guard(true);
       } else {
-        cache_.AddCheck(instance_cid_or_function_, instance_type_arguments_,
-                        instantiator_type_arguments_, function_type_arguments_,
+        cache_.AddCheck(instance_cid_or_function_, type_,
+                        instance_type_arguments_, instantiator_type_arguments_,
+                        function_type_arguments_,
                         parent_function_type_arguments_,
                         delayed_function_type_arguments_, Bool::True());
       }
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index db22d30..eb9c647 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -17292,6 +17292,7 @@
 
 void SubtypeTestCache::AddCheck(
     const Object& instance_class_id_or_function,
+    const AbstractType& destination_type,
     const TypeArguments& instance_type_arguments,
     const TypeArguments& instantiator_type_arguments,
     const TypeArguments& function_type_arguments,
@@ -17312,6 +17313,7 @@
   auto entry = entries[old_num];
   ASSERT(entry.Get<kInstanceClassIdOrFunction>() == Object::null());
   entry.Set<kInstanceClassIdOrFunction>(instance_class_id_or_function);
+  entry.Set<kDestinationType>(destination_type);
   entry.Set<kInstanceTypeArguments>(instance_type_arguments);
   entry.Set<kInstantiatorTypeArguments>(instantiator_type_arguments);
   entry.Set<kFunctionTypeArguments>(function_type_arguments);
@@ -17329,6 +17331,7 @@
 void SubtypeTestCache::GetCheck(
     intptr_t ix,
     Object* instance_class_id_or_function,
+    AbstractType* destination_type,
     TypeArguments* instance_type_arguments,
     TypeArguments* instantiator_type_arguments,
     TypeArguments* function_type_arguments,
@@ -17344,6 +17347,7 @@
   SubtypeTestCacheTable entries(data);
   auto entry = entries[ix];
   *instance_class_id_or_function = entry.Get<kInstanceClassIdOrFunction>();
+  *destination_type = entry.Get<kDestinationType>();
   *instance_type_arguments = entry.Get<kInstanceTypeArguments>();
   *instantiator_type_arguments = entry.Get<kInstantiatorTypeArguments>();
   *function_type_arguments = entry.Get<kFunctionTypeArguments>();
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 46354d0..eb290ea 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -6842,24 +6842,29 @@
   enum Entries {
     kTestResult = 0,
     kInstanceClassIdOrFunction = 1,
-    kInstanceTypeArguments = 2,
-    kInstantiatorTypeArguments = 3,
-    kFunctionTypeArguments = 4,
-    kInstanceParentFunctionTypeArguments = 5,
-    kInstanceDelayedFunctionTypeArguments = 6,
-    kTestEntryLength = 7,
+    kDestinationType = 2,
+    kInstanceTypeArguments = 3,
+    kInstantiatorTypeArguments = 4,
+    kFunctionTypeArguments = 5,
+    kInstanceParentFunctionTypeArguments = 6,
+    kInstanceDelayedFunctionTypeArguments = 7,
+    kTestEntryLength = 8,
   };
 
-  intptr_t NumberOfChecks() const;
+  virtual intptr_t NumberOfChecks() const;
+  // For a non-dynamic SubtypeTestCache, destination_type is unused.
   void AddCheck(const Object& instance_class_id_or_function,
+                const AbstractType& destination_type,
                 const TypeArguments& instance_type_arguments,
                 const TypeArguments& instantiator_type_arguments,
                 const TypeArguments& function_type_arguments,
                 const TypeArguments& instance_parent_function_type_arguments,
                 const TypeArguments& instance_delayed_type_arguments,
                 const Bool& test_result) const;
+  // For a non-dynamic SubtypeTestCache, destination_type is always set to null.
   void GetCheck(intptr_t ix,
                 Object* instance_class_id_or_function,
+                AbstractType* destination_type,
                 TypeArguments* instance_type_arguments,
                 TypeArguments* instantiator_type_arguments,
                 TypeArguments* function_type_arguments,
@@ -11375,6 +11380,7 @@
 using SubtypeTestCacheTable = ArrayOfTuplesView<SubtypeTestCache::Entries,
                                                 std::tuple<Object,
                                                            Object,
+                                                           AbstractType,
                                                            TypeArguments,
                                                            TypeArguments,
                                                            TypeArguments,
diff --git a/runtime/vm/object_test.cc b/runtime/vm/object_test.cc
index 01a7024..7f421dd 100644
--- a/runtime/vm/object_test.cc
+++ b/runtime/vm/object_test.cc
@@ -3075,35 +3075,45 @@
 ISOLATE_UNIT_TEST_CASE(SubtypeTestCache) {
   SafepointMutexLocker ml(thread->isolate_group()->subtype_test_cache_mutex());
 
-  String& class_name = String::Handle(Symbols::New(thread, "EmptyClass"));
+  String& class1_name = String::Handle(Symbols::New(thread, "EmptyClass1"));
   Script& script = Script::Handle();
-  const Class& empty_class =
-      Class::Handle(CreateDummyClass(class_name, script));
+  const Class& empty_class1 =
+      Class::Handle(CreateDummyClass(class1_name, script));
+  String& class2_name = String::Handle(Symbols::New(thread, "EmptyClass2"));
+  const Class& empty_class2 =
+      Class::Handle(CreateDummyClass(class2_name, script));
   SubtypeTestCache& cache = SubtypeTestCache::Handle(SubtypeTestCache::New());
   EXPECT(!cache.IsNull());
   EXPECT_EQ(0, cache.NumberOfChecks());
-  const Object& class_id_or_fun = Object::Handle(Smi::New(empty_class.id()));
+  const Object& class_id_or_fun = Object::Handle(Smi::New(empty_class1.id()));
+  const AbstractType& dest_type =
+      AbstractType::Handle(Type::NewNonParameterizedType(empty_class2));
   const TypeArguments& targ_0 = TypeArguments::Handle(TypeArguments::New(2));
   const TypeArguments& targ_1 = TypeArguments::Handle(TypeArguments::New(3));
   const TypeArguments& targ_2 = TypeArguments::Handle(TypeArguments::New(4));
   const TypeArguments& targ_3 = TypeArguments::Handle(TypeArguments::New(5));
   const TypeArguments& targ_4 = TypeArguments::Handle(TypeArguments::New(6));
-  cache.AddCheck(class_id_or_fun, targ_0, targ_1, targ_2, targ_3, targ_4,
-                 Bool::True());
+  cache.AddCheck(class_id_or_fun, dest_type, targ_0, targ_1, targ_2, targ_3,
+                 targ_4, Bool::True());
   EXPECT_EQ(1, cache.NumberOfChecks());
   Object& test_class_id_or_fun = Object::Handle();
+  AbstractType& test_dest_type = AbstractType::Handle();
   TypeArguments& test_targ_0 = TypeArguments::Handle();
   TypeArguments& test_targ_1 = TypeArguments::Handle();
   TypeArguments& test_targ_2 = TypeArguments::Handle();
   TypeArguments& test_targ_3 = TypeArguments::Handle();
   TypeArguments& test_targ_4 = TypeArguments::Handle();
   Bool& test_result = Bool::Handle();
-  cache.GetCheck(0, &test_class_id_or_fun, &test_targ_0, &test_targ_1,
-                 &test_targ_2, &test_targ_3, &test_targ_4, &test_result);
+  cache.GetCheck(0, &test_class_id_or_fun, &test_dest_type, &test_targ_0,
+                 &test_targ_1, &test_targ_2, &test_targ_3, &test_targ_4,
+                 &test_result);
   EXPECT_EQ(class_id_or_fun.raw(), test_class_id_or_fun.raw());
+  EXPECT_EQ(dest_type.raw(), test_dest_type.raw());
   EXPECT_EQ(targ_0.raw(), test_targ_0.raw());
   EXPECT_EQ(targ_1.raw(), test_targ_1.raw());
   EXPECT_EQ(targ_2.raw(), test_targ_2.raw());
+  EXPECT_EQ(targ_3.raw(), test_targ_3.raw());
+  EXPECT_EQ(targ_4.raw(), test_targ_4.raw());
   EXPECT_EQ(Bool::True().raw(), test_result.raw());
 }
 
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index f2c86a2..93c3f4f 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -556,21 +556,35 @@
   }
   const Function& function =
       Function::Handle(caller_frame->LookupDartFunction());
-  OS::PrintErr(" -> Function %s\n", function.ToFullyQualifiedCString());
+  if (function.IsInvokeFieldDispatcher() ||
+      function.IsNoSuchMethodDispatcher()) {
+    const auto& args_desc_array = Array::Handle(function.saved_args_desc());
+    const ArgumentsDescriptor args_desc(args_desc_array);
+    OS::PrintErr(" -> Function %s [%s]\n", function.ToFullyQualifiedCString(),
+                 args_desc.ToCString());
+  } else {
+    OS::PrintErr(" -> Function %s\n", function.ToFullyQualifiedCString());
+  }
 }
 
-// This updates the type test cache, an array containing 5-value elements
-// (instance class (or function if the instance is a closure), instance type
-// arguments, instantiator type arguments, function type arguments,
-// and test_result). It can be applied to classes with type arguments in which
-// case it contains just the result of the class subtype test, not including the
-// evaluation of type arguments.
+// This updates the type test cache, an array containing 8 elements:
+// - instance class (or function if the instance is a closure)
+// - instance type arguments (null if the instance class is not generic)
+// - instantiator type arguments (null if the type is instantiated)
+// - function type arguments (null if the type is instantiated)
+// - instance parent function type arguments (null if instance is not a closure)
+// - instance delayed type arguments (null if instance is not a closure)
+// - destination type (null if the type was known at compile time)
+// - test result
+// It can be applied to classes with type arguments in which case it contains
+// just the result of the class subtype test, not including the evaluation of
+// type arguments.
 // This operation is currently very slow (lookup of code is not efficient yet).
 static void UpdateTypeTestCache(
     Zone* zone,
     Thread* thread,
     const Instance& instance,
-    const AbstractType& type,
+    const AbstractType& destination_type,
     const TypeArguments& instantiator_type_arguments,
     const TypeArguments& function_type_arguments,
     const Bool& result,
@@ -628,6 +642,7 @@
     ASSERT(instance_delayed_type_arguments.IsNull() ||
            instance_delayed_type_arguments.IsCanonical());
     auto& last_instance_class_id_or_function = Object::Handle(zone);
+    auto& last_destination_type = AbstractType::Handle(zone);
     auto& last_instance_type_arguments = TypeArguments::Handle(zone);
     auto& last_instantiator_type_arguments = TypeArguments::Handle(zone);
     auto& last_function_type_arguments = TypeArguments::Handle(zone);
@@ -636,11 +651,12 @@
     auto& last_instance_delayed_type_arguments = TypeArguments::Handle(zone);
     Bool& last_result = Bool::Handle(zone);
     for (intptr_t i = 0; i < len; ++i) {
-      new_cache.GetCheck(
-          i, &last_instance_class_id_or_function, &last_instance_type_arguments,
-          &last_instantiator_type_arguments, &last_function_type_arguments,
-          &last_instance_parent_function_type_arguments,
-          &last_instance_delayed_type_arguments, &last_result);
+      new_cache.GetCheck(i, &last_instance_class_id_or_function,
+                         &last_destination_type, &last_instance_type_arguments,
+                         &last_instantiator_type_arguments,
+                         &last_function_type_arguments,
+                         &last_instance_parent_function_type_arguments,
+                         &last_instance_delayed_type_arguments, &last_result);
       if ((last_instance_class_id_or_function.raw() ==
            instance_class_id_or_function.raw()) &&
           (last_instance_type_arguments.raw() ==
@@ -652,57 +668,72 @@
           (last_instance_parent_function_type_arguments.raw() ==
            instance_parent_function_type_arguments.raw()) &&
           (last_instance_delayed_type_arguments.raw() ==
-           instance_delayed_type_arguments.raw())) {
+           instance_delayed_type_arguments.raw()) &&
+          (last_destination_type.raw() == destination_type.raw())) {
+        if (!FLAG_enable_isolate_groups) {
+          FATAL("Duplicate subtype test cache entry");
+        }
         // Some other isolate might have updated the cache between entry was
         // found missing and now.
         return;
       }
     }
-    new_cache.AddCheck(instance_class_id_or_function, instance_type_arguments,
-                       instantiator_type_arguments, function_type_arguments,
+    new_cache.AddCheck(instance_class_id_or_function, destination_type,
+                       instance_type_arguments, instantiator_type_arguments,
+                       function_type_arguments,
                        instance_parent_function_type_arguments,
                        instance_delayed_type_arguments, result);
     if (FLAG_trace_type_checks) {
-      AbstractType& test_type = AbstractType::Handle(zone, type.raw());
+      AbstractType& test_type =
+          AbstractType::Handle(zone, destination_type.raw());
       if (!test_type.IsInstantiated()) {
-        test_type =
-            type.InstantiateFrom(instantiator_type_arguments,
-                                 function_type_arguments, kAllFree, Heap::kNew);
+        test_type = test_type.InstantiateFrom(instantiator_type_arguments,
+                                              function_type_arguments, kAllFree,
+                                              Heap::kNew);
       }
       const auto& type_class = Class::Handle(zone, test_type.type_class());
       const auto& instance_class_name =
           String::Handle(zone, instance_class.Name());
-      OS::PrintErr(
-          "  Updated test cache %#" Px " ix: %" Pd
-          " with (cid-or-fun:"
-          " %#" Px ", type-args: %#" Px ", i-type-args: %#" Px
-          ", "
-          "f-type-args: %#" Px ", p-type-args: %#" Px
-          ", "
-          "d-type-args: %#" Px
-          ", result: %s)\n"
-          "    instance  [class: (%#" Px " '%s' cid: %" Pd
-          "),    type-args: %#" Px
-          " %s]\n"
-          "    test-type [class: (%#" Px " '%s' cid: %" Pd
-          "), i-type-args: %#" Px " %s, f-type-args: %#" Px " %s]\n",
-          static_cast<uword>(new_cache.raw()), len,
+      TextBuffer buffer(256);
+      buffer.Printf("  Updated test cache %#" Px " ix: %" Pd " with ",
+                    static_cast<uword>(new_cache.raw()), len);
+      buffer.Printf(
+          "[ %#" Px ", %#" Px ", %#" Px ", %#" Px ", %#" Px ", %#" Px "",
           static_cast<uword>(instance_class_id_or_function.raw()),
           static_cast<uword>(instance_type_arguments.raw()),
           static_cast<uword>(instantiator_type_arguments.raw()),
           static_cast<uword>(function_type_arguments.raw()),
           static_cast<uword>(instance_parent_function_type_arguments.raw()),
-          static_cast<uword>(instance_delayed_type_arguments.raw()),
-          result.ToCString(), static_cast<uword>(instance_class.raw()),
-          instance_class_name.ToCString(), instance_class.id(),
-          static_cast<uword>(instance_type_arguments.raw()),
-          instance_type_arguments.ToCString(),
-          static_cast<uword>(type_class.raw()),
-          String::Handle(zone, type_class.Name()).ToCString(), type_class.id(),
-          static_cast<uword>(instantiator_type_arguments.raw()),
-          instantiator_type_arguments.ToCString(),
-          static_cast<uword>(function_type_arguments.raw()),
-          function_type_arguments.ToCString());
+          static_cast<uword>(instance_delayed_type_arguments.raw()));
+      buffer.Printf(", %#" Px "", static_cast<uword>(destination_type.raw()));
+      buffer.Printf(" %#" Px " ]\n", static_cast<uword>(result.raw()));
+      buffer.Printf("    tested type: %s (%" Pd ")\n", test_type.ToCString(),
+                    type_class.id());
+      buffer.Printf("    destination type: %s\n", destination_type.ToCString());
+      buffer.Printf("    instance: %s\n", instance.ToCString());
+      if (instance_class.IsClosureClass()) {
+        buffer.Printf("    function: %s\n",
+                      Function::Cast(instance_class_id_or_function)
+                          .ToFullyQualifiedCString());
+        buffer.Printf("    closure instantiator function type arguments: %s\n",
+                      instance_type_arguments.ToCString());
+        buffer.Printf("    closure parent function type arguments: %s\n",
+                      instance_parent_function_type_arguments.ToCString());
+        buffer.Printf("    closure delayed function type arguments: %s\n",
+                      instance_parent_function_type_arguments.ToCString());
+      } else {
+        buffer.Printf("    class: %s (%" Pd ")\n",
+                      instance_class_name.ToCString(),
+                      Smi::Cast(instance_class_id_or_function).Value());
+        buffer.Printf("    instance type arguments: %s\n",
+                      instance_type_arguments.ToCString());
+      }
+      buffer.Printf("    instantiator type arguments: %s\n",
+                    instantiator_type_arguments.ToCString());
+      buffer.Printf("    function type arguments: %s\n",
+                    function_type_arguments.ToCString());
+      buffer.Printf("    result: %s\n", result.ToCString());
+      OS::PrintErr("%s", buffer.buffer());
     }
   }
 }
diff --git a/runtime/vm/stub_code_list.h b/runtime/vm/stub_code_list.h
index 2725513..3d9a9b9 100644
--- a/runtime/vm/stub_code_list.h
+++ b/runtime/vm/stub_code_list.h
@@ -87,9 +87,9 @@
   V(TwoArgsUnoptimizedStaticCall)                                              \
   V(AssertSubtype)                                                             \
   V(Subtype1TestCache)                                                         \
-  V(Subtype2TestCache)                                                         \
-  V(Subtype4TestCache)                                                         \
-  V(Subtype6TestCache)                                                         \
+  V(Subtype3TestCache)                                                         \
+  V(Subtype5TestCache)                                                         \
+  V(Subtype7TestCache)                                                         \
   VM_TYPE_TESTING_STUB_CODE_LIST(V)                                            \
   V(CallClosureNoSuchMethod)                                                   \
   V(FrameAwaitingMaterialization)                                              \
diff --git a/tests/lib/js/implements_static_test.dart b/tests/lib/js/implements_static_test.dart
new file mode 100644
index 0000000..ca7e8f0
--- /dev/null
+++ b/tests/lib/js/implements_static_test.dart
@@ -0,0 +1,158 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Tests classes implementing other interfaces with JS interop. Only tests
+// getters and assumes other instance members work similarly to avoid bloated
+// tests.
+
+@JS()
+library implements_static_test;
+
+import 'package:js/js.dart';
+
+// Normal and abstract classes for Dart, JS, and anonymous classes.
+class DartClass {
+  int get dartGetter => 0;
+}
+
+abstract class AbstractDartClass {
+  int get abstractDartGetter;
+}
+
+@JS()
+class JSClass {
+  external int get jsGetter;
+}
+
+@JS()
+abstract class AbstractJSClass {
+  external int get abstractJsGetter;
+}
+
+@JS()
+@anonymous
+class AnonymousClass {
+  external int get anonymousGetter;
+}
+
+@JS()
+@anonymous
+abstract class AbstractAnonymousClass {
+  external int get abstractAnonymousGetter;
+}
+
+// Dart classes that implement all the other JS type interfaces.
+class DartClassImplementsJSClass implements JSClass {
+  int get jsGetter => 0;
+}
+
+class DartClassImplementsAbstractJSClass implements AbstractJSClass {
+  int get abstractJsGetter => 0;
+}
+
+class DartClassImplementsAnonymousClass implements AnonymousClass {
+  int get anonymousGetter => 0;
+}
+
+class DartClassImplementsAbstractAnonymousClass
+    implements AbstractAnonymousClass {
+  int get abstractAnonymousGetter => 0;
+}
+
+// JS classes that implement all the other interfaces.
+@JS()
+class JSClassImplementsDartClass implements DartClass {
+  external int get dartGetter;
+}
+
+@JS()
+class JSClassImplementsAbstractDartClass implements AbstractDartClass {
+  external int get abstractDartGetter;
+}
+
+@JS()
+class JSClassImplementsJSClass implements JSClass {
+  external int get jsGetter;
+}
+
+@JS()
+class JSClassImplementsAbstractJSClass implements AbstractJSClass {
+  external int get abstractJsGetter;
+}
+
+@JS()
+class JSClassImplementsAnonymousClass implements AnonymousClass {
+  external int get anonymousGetter;
+}
+
+@JS()
+class JSClassImplementsAbstractAnonymousClass
+    implements AbstractAnonymousClass {
+  external int get abstractAnonymousGetter;
+}
+
+// Anonymous classes that implement all the other interfaces.
+@JS()
+@anonymous
+class AnonymousClassImplementsDartClass implements DartClass {
+  external int get dartGetter;
+}
+
+@JS()
+@anonymous
+class AnonymousClassImplementsAbstractDartClass implements AbstractDartClass {
+  external int get abstractDartGetter;
+}
+
+@JS()
+@anonymous
+class AnonymousClassImplementsJSClass implements JSClass {
+  external int get jsGetter;
+}
+
+@JS()
+@anonymous
+class AnonymousClassImplementsAbstractJSClass implements AbstractJSClass {
+  external int get abstractJsGetter;
+}
+
+@JS()
+@anonymous
+class AnonymousClassImplementsAnonymousClass implements AnonymousClass {
+  external int get anonymousGetter;
+}
+
+@JS()
+@anonymous
+class AnonymousClassImplementsAbstractAnonymousClass
+    implements AbstractAnonymousClass {
+  external int get abstractAnonymousGetter;
+}
+
+// Dart, JS, and anonymous classes implementing multiple interfaces.
+class DartClassImplementsMultipleInterfaces
+    implements DartClass, AbstractJSClass, AnonymousClass {
+  int get dartGetter => 0;
+  int get abstractJsGetter => 0;
+  int get anonymousGetter => 0;
+}
+
+@JS()
+class JSClassImplementsMultipleInterfaces
+    implements AbstractDartClass, JSClass, AbstractAnonymousClass {
+  external int get abstractDartGetter;
+  external int get jsGetter;
+  external int get abstractAnonymousGetter;
+}
+
+@JS()
+@anonymous
+class AnonymousClassImplementsMultipleInterfaces
+    implements DartClass, JSClass, AbstractAnonymousClass {
+  external int get dartGetter;
+  external int get jsGetter;
+  external int get abstractAnonymousGetter;
+}
+
+void main() {}
diff --git a/tools/VERSION b/tools/VERSION
index 9c520c2..3545891 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 12
 PATCH 0
-PRERELEASE 55
+PRERELEASE 56
 PRERELEASE_PATCH 0
\ No newline at end of file