| 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 |