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<<a href="#type_AvailableSuggestion">AvailableSuggestion</a>></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<<a href="#type_BulkFixDetail">BulkFixDetail</a>></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, ¬_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(¬_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(¬_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(¬_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, ¬_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(¬_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, ¬_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(¬_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, ¬_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(¬_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(¬_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(¬_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(¬_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(¬_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(¬_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(¬_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