OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library dart2js_incremental.library_updater; | 5 library dart2js_incremental.library_updater; |
6 | 6 |
7 import 'dart:async' show | 7 import 'dart:async' show |
8 Future; | 8 Future; |
9 | 9 |
10 import 'dart:convert' show | 10 import 'dart:convert' show |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
57 Difference, | 57 Difference, |
58 computeDifference; | 58 computeDifference; |
59 | 59 |
60 typedef void Logger(message); | 60 typedef void Logger(message); |
61 | 61 |
62 typedef bool Reuser( | 62 typedef bool Reuser( |
63 Token diffToken, | 63 Token diffToken, |
64 PartialElement before, | 64 PartialElement before, |
65 PartialElement after); | 65 PartialElement after); |
66 | 66 |
| 67 class FailedUpdate { |
| 68 /// Either an [Element] or a [Difference]. |
| 69 final context; |
| 70 final String message; |
| 71 |
| 72 FailedUpdate(this.context, this.message); |
| 73 |
| 74 String toString() { |
| 75 if (context == null) return '$message'; |
| 76 return 'In $context:\n $message'; |
| 77 } |
| 78 } |
| 79 |
67 // TODO(ahe): Generalize this class. For now only works for Compiler.mainApp, | 80 // TODO(ahe): Generalize this class. For now only works for Compiler.mainApp, |
68 // and only if that library has exactly one compilation unit. | 81 // and only if that library has exactly one compilation unit. |
69 class LibraryUpdater { | 82 class LibraryUpdater { |
70 final Compiler compiler; | 83 final Compiler compiler; |
71 | 84 |
72 final api.CompilerInputProvider inputProvider; | 85 final api.CompilerInputProvider inputProvider; |
73 | 86 |
74 final Logger logTime; | 87 final Logger logTime; |
75 | 88 |
76 final Logger logVerbose; | 89 final Logger logVerbose; |
77 | 90 |
78 // TODO(ahe): Get rid of this field. It assumes that only one library has | 91 // TODO(ahe): Get rid of this field. It assumes that only one library has |
79 // changed. | 92 // changed. |
80 final Uri uri; | 93 final Uri uri; |
81 | 94 |
82 // When [true], updates must be applied (using [applyUpdates]) before the | 95 final List<Update> updates = <Update>[]; |
83 // [compiler]'s state correctly reflects the updated program. | |
84 bool hasPendingUpdates = false; | |
85 | 96 |
86 bool onlySimpleUpdates = true; | 97 final List<FailedUpdate> _failedUpdates = <FailedUpdate>[]; |
87 | |
88 final List<Update> updates = <Update>[]; | |
89 | 98 |
90 LibraryUpdater( | 99 LibraryUpdater( |
91 this.compiler, | 100 this.compiler, |
92 this.inputProvider, | 101 this.inputProvider, |
93 this.uri, | 102 this.uri, |
94 this.logTime, | 103 this.logTime, |
95 this.logVerbose); | 104 this.logVerbose); |
96 | 105 |
| 106 /// When [true], updates must be applied (using [applyUpdates]) before the |
| 107 /// [compiler]'s state correctly reflects the updated program. |
| 108 bool get hasPendingUpdates => !updates.isEmpty; |
| 109 |
| 110 bool get failed => !_failedUpdates.isEmpty; |
| 111 |
97 JavaScriptBackend get backend => compiler.backend; | 112 JavaScriptBackend get backend => compiler.backend; |
98 | 113 |
99 Namer get namer => backend.namer; | 114 Namer get namer => backend.namer; |
100 | 115 |
101 CodeEmitterTask get emitter => backend.emitter; | 116 CodeEmitterTask get emitter => backend.emitter; |
102 | 117 |
103 /// Used as tear-off passed to [LibraryLoaderTask.resetAsync]. | 118 /// Used as tear-off passed to [LibraryLoaderTask.resetAsync]. |
104 Future<bool> reuseLibrary(LibraryElement library) { | 119 Future<bool> reuseLibrary(LibraryElement library) { |
105 assert(compiler != null); | 120 assert(compiler != null); |
106 if (library.isPlatformLibrary || library.isPackageLibrary) { | 121 if (library.isPlatformLibrary || library.isPackageLibrary) { |
(...skipping 27 matching lines...) Expand all Loading... |
134 logTime("Source did change"); | 149 logTime("Source did change"); |
135 Script sourceScript = new Script( | 150 Script sourceScript = new Script( |
136 uri, uri, new StringSourceFile('$uri', newSource)); | 151 uri, uri, new StringSourceFile('$uri', newSource)); |
137 var dartPrivacyIsBroken = compiler.libraryLoader; | 152 var dartPrivacyIsBroken = compiler.libraryLoader; |
138 LibraryElement newLibrary = dartPrivacyIsBroken.createLibrarySync( | 153 LibraryElement newLibrary = dartPrivacyIsBroken.createLibrarySync( |
139 null, sourceScript, uri); | 154 null, sourceScript, uri); |
140 logTime('New library synthesized.'); | 155 logTime('New library synthesized.'); |
141 return canReuseScopeContainerElement(library, newLibrary); | 156 return canReuseScopeContainerElement(library, newLibrary); |
142 } | 157 } |
143 | 158 |
| 159 bool cannotReuse(context, String message) { |
| 160 _failedUpdates.add(new FailedUpdate(context, message)); |
| 161 logVerbose(message); |
| 162 return false; |
| 163 } |
| 164 |
144 bool canReuseScopeContainerElement( | 165 bool canReuseScopeContainerElement( |
145 ScopeContainerElement element, | 166 ScopeContainerElement element, |
146 ScopeContainerElement newElement) { | 167 ScopeContainerElement newElement) { |
147 List<Difference> differences = computeDifference(element, newElement); | 168 List<Difference> differences = computeDifference(element, newElement); |
148 logTime('Differences computed.'); | 169 logTime('Differences computed.'); |
149 for (Difference difference in differences) { | 170 for (Difference difference in differences) { |
150 logTime('Looking at difference: $difference'); | 171 logTime('Looking at difference: $difference'); |
151 if (difference.before == null || difference.after == null) { | 172 if (difference.before == null || difference.after == null) { |
152 logVerbose('Scope changed in $difference'); | 173 cannotReuse(difference, "Can't reuse; Scope changed."); |
153 // Scope changed, don't reuse library. | 174 continue; |
154 onlySimpleUpdates = false; | |
155 return false; | |
156 } | 175 } |
157 Token diffToken = difference.token; | 176 Token diffToken = difference.token; |
158 if (diffToken == null) { | 177 if (diffToken == null) { |
159 logVerbose('No token stored in difference.'); | 178 cannotReuse(difference, "No difference token."); |
160 onlySimpleUpdates = false; | 179 continue; |
161 return false; | |
162 } | 180 } |
163 if (difference.after is! PartialElement && | 181 if (difference.after is! PartialElement && |
164 difference.before is! PartialElement) { | 182 difference.before is! PartialElement) { |
165 logVerbose('Not a PartialElement: $difference'); | 183 cannotReuse(difference, "Don't know how to recompile."); |
166 // Don't know how to recompile element. | 184 continue; |
167 onlySimpleUpdates = false; | |
168 return false; | |
169 } | 185 } |
170 PartialElement before = difference.before; | 186 PartialElement before = difference.before; |
171 PartialElement after = difference.after; | 187 PartialElement after = difference.after; |
172 | 188 |
173 Reuser reuser; | 189 Reuser reuser; |
174 | 190 |
175 if (before is PartialFunctionElement && after is PartialFunctionElement) { | 191 if (before is PartialFunctionElement && after is PartialFunctionElement) { |
176 reuser = canReuseFunction; | 192 reuser = canReuseFunction; |
177 } else if (before is PartialClassElement && | 193 } else if (before is PartialClassElement && |
178 after is PartialClassElement) { | 194 after is PartialClassElement) { |
179 reuser = canReuseClass; | 195 reuser = canReuseClass; |
180 } else { | 196 } else { |
181 reuser = cannotReuse; | 197 reuser = unableToReuse; |
182 } | 198 } |
183 if (!reuser(diffToken, before, after)) { | 199 if (!reuser(diffToken, before, after)) { |
184 onlySimpleUpdates = false; | 200 assert(!_failedUpdates.isEmpty); |
185 return false; | 201 continue; |
186 } | 202 } |
187 } | 203 } |
188 hasPendingUpdates = true; | |
189 | 204 |
190 return true; | 205 return _failedUpdates.isEmpty; |
191 } | 206 } |
192 | 207 |
193 /// Returns true if function [before] can be reused to reflect the changes in | 208 /// Returns true if function [before] can be reused to reflect the changes in |
194 /// [after]. | 209 /// [after]. |
195 /// | 210 /// |
196 /// If [before] can be reused, an update (patch) is added to [updates]. | 211 /// If [before] can be reused, an update (patch) is added to [updates]. |
197 bool canReuseFunction( | 212 bool canReuseFunction( |
198 Token diffToken, | 213 Token diffToken, |
199 PartialFunctionElement before, | 214 PartialFunctionElement before, |
200 PartialFunctionElement after) { | 215 PartialFunctionElement after) { |
201 FunctionExpression node = | 216 FunctionExpression node = |
202 after.parseNode(compiler).asFunctionExpression(); | 217 after.parseNode(compiler).asFunctionExpression(); |
203 if (node == null) { | 218 if (node == null) { |
204 logVerbose('Not a function expression.'); | 219 return cannotReuse(after, "Not a function expression: '$node'"); |
205 return false; | |
206 } | 220 } |
207 Token last = after.endToken; | 221 Token last = after.endToken; |
208 if (node.body != null) { | 222 if (node.body != null) { |
209 last = node.body.getBeginToken(); | 223 last = node.body.getBeginToken(); |
210 } | 224 } |
211 if (isTokenBetween(diffToken, after.beginToken, last)) { | 225 if (isTokenBetween(diffToken, after.beginToken, last)) { |
212 logVerbose('Signature changed.'); | 226 return cannotReuse(after, 'Signature changed.'); |
213 return false; | |
214 } | 227 } |
215 logVerbose('Simple modification of ${after} detected'); | 228 logVerbose('Simple modification of ${after} detected'); |
216 updates.add(new FunctionUpdate(compiler, before, after)); | 229 updates.add(new FunctionUpdate(compiler, before, after)); |
217 return true; | 230 return true; |
218 } | 231 } |
219 | 232 |
220 bool canReuseClass( | 233 bool canReuseClass( |
221 Token diffToken, | 234 Token diffToken, |
222 PartialClassElement before, | 235 PartialClassElement before, |
223 PartialClassElement after) { | 236 PartialClassElement after) { |
224 ClassNode node = after.parseNode(compiler).asClassNode(); | 237 ClassNode node = after.parseNode(compiler).asClassNode(); |
225 if (node == null) { | 238 if (node == null) { |
226 logVerbose('Not a ClassNode.'); | 239 return cannotReuse(after, "Not a ClassNode: '$node'"); |
227 return false; | |
228 } | 240 } |
229 NodeList body = node.body; | 241 NodeList body = node.body; |
230 if (body == null) { | 242 if (body == null) { |
231 logVerbose('Class has no body.'); | 243 return cannotReuse(after, "Class has no body."); |
232 return false; | |
233 } | 244 } |
234 if (isTokenBetween(diffToken, node.beginToken, body.beginToken)) { | 245 if (isTokenBetween(diffToken, node.beginToken, body.beginToken)) { |
235 logVerbose('Class header changed.'); | 246 return cannotReuse(after, "Class header changed."); |
236 return false; | |
237 } | 247 } |
238 logVerbose('Simple modification of ${after} detected'); | 248 logVerbose('Simple modification of ${after} detected'); |
239 return canReuseScopeContainerElement(before, after); | 249 return canReuseScopeContainerElement(before, after); |
240 } | 250 } |
241 | 251 |
242 bool isTokenBetween(Token token, Token first, Token last) { | 252 bool isTokenBetween(Token token, Token first, Token last) { |
243 Token current = first; | 253 Token current = first; |
244 while (current != last && current.kind != EOF_TOKEN) { | 254 while (current != last && current.kind != EOF_TOKEN) { |
245 if (current == token) { | 255 if (current == token) { |
246 return true; | 256 return true; |
247 } | 257 } |
248 current = current.next; | 258 current = current.next; |
249 } | 259 } |
250 return false; | 260 return false; |
251 } | 261 } |
252 | 262 |
253 bool cannotReuse( | 263 bool unableToReuse( |
254 Token diffToken, | 264 Token diffToken, |
255 PartialElement before, | 265 PartialElement before, |
256 PartialElement after) { | 266 PartialElement after) { |
257 logVerbose( | 267 return cannotReuse( |
| 268 after, |
258 'Unhandled change:' | 269 'Unhandled change:' |
259 ' ${before} (${before.runtimeType} -> ${after.runtimeType}).'); | 270 ' ${before} (${before.runtimeType} -> ${after.runtimeType}).'); |
260 return false; | |
261 } | 271 } |
262 | 272 |
263 List<Element> applyUpdates() { | 273 List<Element> applyUpdates() { |
264 if (!onlySimpleUpdates) { | 274 if (!_failedUpdates.isEmpty) { |
265 throw new StateError("Can't compute update."); | 275 throw new StateError( |
| 276 "Can't compute update.\n\n${_failedUpdates.join('\n\n')}"); |
266 } | 277 } |
267 return updates.map((Update update) => update.apply()).toList(); | 278 return updates.map((Update update) => update.apply()).toList(); |
268 } | 279 } |
269 | 280 |
270 String computeUpdateJs() { | 281 String computeUpdateJs() { |
271 List<Element> updatedElements = applyUpdates(); | 282 List<Element> updatedElements = applyUpdates(); |
272 if (compiler.progress != null) { | 283 if (compiler.progress != null) { |
273 compiler.progress.reset(); | 284 compiler.progress.reset(); |
274 } | 285 } |
275 for (Element element in updatedElements) { | 286 for (Element element in updatedElements) { |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
385 before.getOrSet = after.getOrSet; | 396 before.getOrSet = after.getOrSet; |
386 } | 397 } |
387 | 398 |
388 /// Reset various caches and remove this element from the compiler's internal | 399 /// Reset various caches and remove this element from the compiler's internal |
389 /// state. | 400 /// state. |
390 void reuseElement() { | 401 void reuseElement() { |
391 compiler.forgetElement(before); | 402 compiler.forgetElement(before); |
392 before.reuseElement(); | 403 before.reuseElement(); |
393 } | 404 } |
394 } | 405 } |
OLD | NEW |