Chromium Code Reviews| 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 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 80 FailedUpdate(this.context, this.message); | 80 FailedUpdate(this.context, this.message); |
| 81 | 81 |
| 82 String toString() { | 82 String toString() { |
| 83 if (context == null) return '$message'; | 83 if (context == null) return '$message'; |
| 84 return 'In $context:\n $message'; | 84 return 'In $context:\n $message'; |
| 85 } | 85 } |
| 86 } | 86 } |
| 87 | 87 |
| 88 // TODO(ahe): Generalize this class. For now only works for Compiler.mainApp, | 88 // TODO(ahe): Generalize this class. For now only works for Compiler.mainApp, |
| 89 // and only if that library has exactly one compilation unit. | 89 // and only if that library has exactly one compilation unit. |
| 90 class LibraryUpdater { | 90 class LibraryUpdater extends JsFeatures { |
| 91 final Compiler compiler; | 91 final Compiler compiler; |
| 92 | 92 |
| 93 final api.CompilerInputProvider inputProvider; | 93 final api.CompilerInputProvider inputProvider; |
| 94 | 94 |
| 95 final Logger logTime; | 95 final Logger logTime; |
| 96 | 96 |
| 97 final Logger logVerbose; | 97 final Logger logVerbose; |
| 98 | 98 |
| 99 // TODO(ahe): Get rid of this field. It assumes that only one library has | 99 // TODO(ahe): Get rid of this field. It assumes that only one library has |
| 100 // changed. | 100 // changed. |
| 101 final Uri uri; | 101 final Uri uri; |
| 102 | 102 |
| 103 final List<Update> updates = <Update>[]; | 103 final List<Update> updates = <Update>[]; |
| 104 | 104 |
| 105 final List<FailedUpdate> _failedUpdates = <FailedUpdate>[]; | 105 final List<FailedUpdate> _failedUpdates = <FailedUpdate>[]; |
| 106 | 106 |
| 107 final Set<ElementX> _elementsToInvalidate = new Set<ElementX>(); | 107 final Set<ElementX> _elementsToInvalidate = new Set<ElementX>(); |
| 108 | 108 |
| 109 final Set<ElementX> _removedElements = new Set<ElementX>(); | |
| 110 | |
| 109 LibraryUpdater( | 111 LibraryUpdater( |
| 110 this.compiler, | 112 this.compiler, |
| 111 this.inputProvider, | 113 this.inputProvider, |
| 112 this.uri, | 114 this.uri, |
| 113 this.logTime, | 115 this.logTime, |
| 114 this.logVerbose); | 116 this.logVerbose); |
| 115 | 117 |
| 116 /// When [true], updates must be applied (using [applyUpdates]) before the | 118 /// When [true], updates must be applied (using [applyUpdates]) before the |
| 117 /// [compiler]'s state correctly reflects the updated program. | 119 /// [compiler]'s state correctly reflects the updated program. |
| 118 bool get hasPendingUpdates => !updates.isEmpty; | 120 bool get hasPendingUpdates => !updates.isEmpty; |
| 119 | 121 |
| 120 bool get failed => !_failedUpdates.isEmpty; | 122 bool get failed => !_failedUpdates.isEmpty; |
| 121 | 123 |
| 122 JavaScriptBackend get backend => compiler.backend; | |
| 123 | |
| 124 Namer get namer => backend.namer; | |
| 125 | |
| 126 CodeEmitterTask get emitter => backend.emitter; | |
| 127 | |
| 128 /// Used as tear-off passed to [LibraryLoaderTask.resetAsync]. | 124 /// Used as tear-off passed to [LibraryLoaderTask.resetAsync]. |
| 129 Future<bool> reuseLibrary(LibraryElement library) { | 125 Future<bool> reuseLibrary(LibraryElement library) { |
| 130 assert(compiler != null); | 126 assert(compiler != null); |
| 131 if (library.isPlatformLibrary || library.isPackageLibrary) { | 127 if (library.isPlatformLibrary || library.isPackageLibrary) { |
| 132 logTime('Reusing $library.'); | 128 logTime('Reusing $library.'); |
| 133 return new Future.value(true); | 129 return new Future.value(true); |
| 134 } else if (library != compiler.mainApp) { | 130 } else if (library != compiler.mainApp) { |
| 135 return new Future.value(false); | 131 return new Future.value(false); |
| 136 } | 132 } |
| 137 return inputProvider(uri).then((bytes) { | 133 return inputProvider(uri).then((bytes) { |
| (...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 243 for (ScopeContainerElement scope in scopesAffectedBy(element, cls)) { | 239 for (ScopeContainerElement scope in scopesAffectedBy(element, cls)) { |
| 244 scanSites(scope, (Element member, DeclarationSite site) { | 240 scanSites(scope, (Element member, DeclarationSite site) { |
| 245 // TODO(ahe): Cache qualifiedNamesIn to avoid quadratic behavior. | 241 // TODO(ahe): Cache qualifiedNamesIn to avoid quadratic behavior. |
| 246 Map<String, List<String>> names = qualifiedNamesIn(site); | 242 Map<String, List<String>> names = qualifiedNamesIn(site); |
| 247 if (canNamesResolveTo(names, element, cls)) { | 243 if (canNamesResolveTo(names, element, cls)) { |
| 248 _elementsToInvalidate.add(member); | 244 _elementsToInvalidate.add(member); |
| 249 } | 245 } |
| 250 }); | 246 }); |
| 251 } | 247 } |
| 252 | 248 |
| 253 // TODO(ahe): Don't modify the class here, instead use an instance of | 249 _removedElements.add(element); |
| 254 // Update. | |
| 255 Link<Element> localMembersReversed = const Link<Element>(); | |
| 256 cls.forEachLocalMember((member) { | |
| 257 if (member != element) { | |
| 258 localMembersReversed = localMembersReversed.prepend(member); | |
| 259 } | |
| 260 }); | |
| 261 cls.localMembersCache = null; | |
| 262 cls.localMembersReversed = localMembersReversed; | |
| 263 cls.localScope.contents.remove(element.name); | |
| 264 | 250 |
| 265 // TODO(ahe): Also compute a patch which removes the function, e.g., | 251 updates.add(new RemovedFunctionUpdate(compiler, element)); |
| 266 // "delete GlobalObject.MyClass.prototype.memberName". | |
| 267 | |
| 268 // TODO(ahe): Also forget [element]. | |
| 269 | 252 |
| 270 return true; | 253 return true; |
| 271 } | 254 } |
| 272 | 255 |
| 273 void scanSites( | 256 void scanSites( |
| 274 Element element, | 257 Element element, |
| 275 void f(ElementX element, DeclarationSite site)) { | 258 void f(ElementX element, DeclarationSite site)) { |
| 276 DeclarationSite site = declarationSite(element); | 259 DeclarationSite site = declarationSite(element); |
| 277 if (site != null) { | 260 if (site != null) { |
| 278 f(element, site); | 261 f(element, site); |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 356 bool unableToReuse( | 339 bool unableToReuse( |
| 357 Token diffToken, | 340 Token diffToken, |
| 358 PartialElement before, | 341 PartialElement before, |
| 359 PartialElement after) { | 342 PartialElement after) { |
| 360 return cannotReuse( | 343 return cannotReuse( |
| 361 after, | 344 after, |
| 362 'Unhandled change:' | 345 'Unhandled change:' |
| 363 ' ${before} (${before.runtimeType} -> ${after.runtimeType}).'); | 346 ' ${before} (${before.runtimeType} -> ${after.runtimeType}).'); |
| 364 } | 347 } |
| 365 | 348 |
| 366 List<Element> applyUpdates() { | 349 List<Element> applyUpdates([List<Update> removals]) { |
|
Johnni Winther
2014/11/14 08:55:26
Document this, especially semantics of the return
ahe
2014/11/14 14:33:12
Will do in a follow-up.
| |
| 350 for (Update update in updates) { | |
| 351 update.captureState(); | |
| 352 } | |
| 367 if (!_failedUpdates.isEmpty) { | 353 if (!_failedUpdates.isEmpty) { |
| 368 throw new StateError( | 354 throw new StateError( |
| 369 "Can't compute update.\n\n${_failedUpdates.join('\n\n')}"); | 355 "Can't compute update.\n\n${_failedUpdates.join('\n\n')}"); |
| 370 } | 356 } |
| 371 for (ElementX element in _elementsToInvalidate) { | 357 for (ElementX element in _elementsToInvalidate) { |
| 372 compiler.forgetElement(element); | 358 compiler.forgetElement(element); |
| 373 element.reuseElement(); | 359 element.reuseElement(); |
| 374 } | 360 } |
| 375 return updates.map((Update update) => update.apply()).toList() | 361 List<Element> elementsToInvalidate = <Element>[]; |
| 376 ..addAll(_elementsToInvalidate); | 362 for (ElementX element in _elementsToInvalidate) { |
| 363 if (!_removedElements.contains(element)) { | |
| 364 elementsToInvalidate.add(element); | |
| 365 } | |
| 366 } | |
| 367 for (Update update in updates) { | |
| 368 Element element = update.apply(); | |
| 369 if (update.isRemoval) { | |
| 370 if (removals != null) { | |
| 371 removals.add(update); | |
| 372 } | |
| 373 } else { | |
| 374 elementsToInvalidate.add(element); | |
| 375 } | |
| 376 } | |
| 377 return elementsToInvalidate; | |
| 377 } | 378 } |
| 378 | 379 |
| 379 String computeUpdateJs() { | 380 String computeUpdateJs() { |
| 380 List<Element> updatedElements = applyUpdates(); | 381 List<Update> removals = <Update>[]; |
| 382 List<Element> updatedElements = applyUpdates(removals); | |
| 381 if (compiler.progress != null) { | 383 if (compiler.progress != null) { |
| 382 compiler.progress.reset(); | 384 compiler.progress.reset(); |
| 383 } | 385 } |
| 384 for (Element element in updatedElements) { | 386 for (Element element in updatedElements) { |
| 385 compiler.enqueuer.resolution.addToWorkList(element); | 387 compiler.enqueuer.resolution.addToWorkList(element); |
| 386 } | 388 } |
| 387 compiler.processQueue(compiler.enqueuer.resolution, null); | 389 compiler.processQueue(compiler.enqueuer.resolution, null); |
| 388 | 390 |
| 389 compiler.phase = Compiler.PHASE_DONE_RESOLVING; | 391 compiler.phase = Compiler.PHASE_DONE_RESOLVING; |
| 390 | 392 |
| 391 for (Element element in updatedElements) { | 393 for (Element element in updatedElements) { |
| 392 compiler.enqueuer.codegen.addToWorkList(element); | 394 compiler.enqueuer.codegen.addToWorkList(element); |
| 393 } | 395 } |
| 394 compiler.processQueue(compiler.enqueuer.codegen, null); | 396 compiler.processQueue(compiler.enqueuer.codegen, null); |
| 395 | 397 |
| 396 List<jsAst.Statement> updates = <jsAst.Statement>[]; | 398 List<jsAst.Statement> updates = <jsAst.Statement>[]; |
| 397 for (Element element in compiler.enqueuer.codegen.newlyEnqueuedElements) { | 399 for (Element element in compiler.enqueuer.codegen.newlyEnqueuedElements) { |
| 398 if (!element.isField) { | 400 if (!element.isField) { |
| 399 updates.add(computeMemberUpdateJs(element)); | 401 updates.add(computeMemberUpdateJs(element)); |
| 400 } | 402 } |
| 401 } | 403 } |
| 404 for (RemovedFunctionUpdate update in removals) { | |
| 405 update.writeUpdateJsOn(updates); | |
| 406 } | |
| 402 | 407 |
| 403 if (updates.length == 1) { | 408 if (updates.length == 1) { |
| 404 return prettyPrintJs(updates.single); | 409 return prettyPrintJs(updates.single); |
| 405 } else { | 410 } else { |
| 406 return prettyPrintJs(js.statement('{#}', [updates])); | 411 return prettyPrintJs(js.statement('{#}', [updates])); |
| 407 } | 412 } |
| 408 } | 413 } |
| 409 | 414 |
| 410 jsAst.Node computeMemberUpdateJs(Element element) { | 415 jsAst.Node computeMemberUpdateJs(Element element) { |
| 411 MemberInfo info = emitter.oldEmitter.containerBuilder | 416 MemberInfo info = emitter.oldEmitter.containerBuilder |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 469 final Compiler compiler; | 474 final Compiler compiler; |
| 470 | 475 |
| 471 PartialElement get before; | 476 PartialElement get before; |
| 472 | 477 |
| 473 PartialElement get after; | 478 PartialElement get after; |
| 474 | 479 |
| 475 Update(this.compiler); | 480 Update(this.compiler); |
| 476 | 481 |
| 477 /// Applies the update to [before] and returns that element. | 482 /// Applies the update to [before] and returns that element. |
| 478 PartialElement apply(); | 483 PartialElement apply(); |
| 484 | |
| 485 bool get isRemoval => false; | |
| 486 | |
| 487 /// Called before any patches are applied to capture any state that is needed | |
| 488 /// later. | |
| 489 void captureState() { | |
| 490 } | |
| 479 } | 491 } |
| 480 | 492 |
| 481 /// Represents an update of a function element. | 493 /// Represents an update of a function element. |
| 482 class FunctionUpdate extends Update { | 494 class FunctionUpdate extends Update with ReuseFunction { |
| 483 final PartialFunctionElement before; | 495 final PartialFunctionElement before; |
| 484 | 496 |
| 485 final PartialFunctionElement after; | 497 final PartialFunctionElement after; |
| 486 | 498 |
| 487 FunctionUpdate(Compiler compiler, this.before, this.after) | 499 FunctionUpdate(Compiler compiler, this.before, this.after) |
| 488 : super(compiler); | 500 : super(compiler); |
| 489 | 501 |
| 490 PartialFunctionElement apply() { | 502 PartialFunctionElement apply() { |
| 491 patchElement(); | 503 patchElement(); |
| 492 reuseElement(); | 504 reuseElement(); |
| 493 return before; | 505 return before; |
| 494 } | 506 } |
| 495 | 507 |
| 496 /// Destructively change the tokens in [before] to match those of [after]. | 508 /// Destructively change the tokens in [before] to match those of [after]. |
| 497 void patchElement() { | 509 void patchElement() { |
| 498 before.beginToken = after.beginToken; | 510 before.beginToken = after.beginToken; |
| 499 before.endToken = after.endToken; | 511 before.endToken = after.endToken; |
| 500 before.getOrSet = after.getOrSet; | 512 before.getOrSet = after.getOrSet; |
| 501 } | 513 } |
| 514 } | |
| 515 | |
| 516 abstract class ReuseFunction { | |
| 517 PartialFunctionElement get before; | |
| 502 | 518 |
| 503 /// Reset various caches and remove this element from the compiler's internal | 519 /// Reset various caches and remove this element from the compiler's internal |
| 504 /// state. | 520 /// state. |
| 505 void reuseElement() { | 521 void reuseElement() { |
| 506 compiler.forgetElement(before); | 522 compiler.forgetElement(before); |
| 507 before.reuseElement(); | 523 before.reuseElement(); |
| 508 } | 524 } |
| 509 } | 525 } |
| 510 | 526 |
| 527 class RemovedFunctionUpdate extends Update with JsFeatures, ReuseFunction { | |
| 528 final PartialFunctionElement element; | |
| 529 | |
| 530 /// Name of property to remove using JavaScript "delete". | |
| 531 String name; | |
| 532 | |
| 533 /// Name of super-alias property to remove using JavaScript "delete". Null | |
| 534 /// for methods that aren't "super aliased" (should imply that this field is | |
| 535 /// null for all non-instance methods). | |
| 536 String superName; | |
| 537 | |
| 538 bool wasStateCaptured = false; | |
| 539 | |
| 540 RemovedFunctionUpdate(Compiler compiler, this.element) | |
| 541 : super(compiler); | |
| 542 | |
| 543 PartialFunctionElement get before => element; | |
| 544 | |
| 545 PartialElement get after => null; | |
| 546 | |
| 547 bool get isRemoval => true; | |
| 548 | |
| 549 void captureState() { | |
| 550 if (wasStateCaptured) throw "captureState was called twice."; | |
| 551 | |
| 552 if (element.isInstanceMember) { | |
| 553 name = namer.getNameOfMember(element); | |
| 554 } | |
| 555 if (backend.isAliasedSuperMember(element)) { | |
| 556 superName = namer.getNameOfAliasedSuperMember(element); | |
| 557 } | |
| 558 | |
| 559 wasStateCaptured = true; | |
| 560 } | |
| 561 | |
| 562 PartialElement apply() { | |
| 563 if (!wasStateCaptured) throw "captureState must be called before apply."; | |
| 564 removeFromEnclosingClass(); | |
| 565 reuseElement(); | |
| 566 return null; | |
| 567 } | |
| 568 | |
| 569 void removeFromEnclosingClass() { | |
| 570 PartialClassElement cls = element.enclosingClass; | |
| 571 | |
| 572 Link<Element> localMembersReversed = const Link<Element>(); | |
| 573 bool foundElement = false; | |
| 574 cls.forEachLocalMember((member) { | |
| 575 if (member != element) { | |
| 576 localMembersReversed = localMembersReversed.prepend(member); | |
| 577 } else { | |
| 578 if (foundElement) { | |
| 579 throw "Found '$element' twice in '$cls'."; | |
| 580 } | |
| 581 foundElement = true; | |
| 582 } | |
| 583 }); | |
| 584 if (!foundElement) { | |
| 585 throw "Don't find '$element' in '$cls'."; | |
| 586 } | |
| 587 cls.localMembersCache = null; | |
| 588 cls.localMembersReversed = localMembersReversed; | |
| 589 cls.localScope.contents.remove(element.name); | |
| 590 | |
| 591 return null; | |
| 592 } | |
| 593 | |
| 594 void writeUpdateJsOn(List<jsAst.Statement> updates) { | |
| 595 if (name == null) { | |
| 596 compiler.internalError(element, '${element.runtimeType}'); | |
| 597 } | |
| 598 if (element.isInstanceMember) { | |
| 599 jsAst.Node elementAccess = namer.elementAccess(element.enclosingClass); | |
| 600 updates.add( | |
| 601 js.statement('delete #.prototype.#', [elementAccess, name])); | |
| 602 | |
| 603 if (superName != null) { | |
| 604 updates.add( | |
| 605 js.statement('delete #.prototype.#', [elementAccess, superName])); | |
| 606 } | |
| 607 } else { | |
| 608 compiler.internalError( | |
| 609 element, 'Removal of non-instance methods not yest supported.'); | |
|
Johnni Winther
2014/11/14 08:55:26
non-instance -> static
ahe
2014/11/14 14:33:12
You missed the misspelling of "yet" ;-)
Regardles
| |
| 610 } | |
| 611 } | |
| 612 } | |
| 613 | |
| 511 Map<String, List<String>> qualifiedNamesIn(PartialElement element) { | 614 Map<String, List<String>> qualifiedNamesIn(PartialElement element) { |
| 512 Token beginToken = element.beginToken; | 615 Token beginToken = element.beginToken; |
| 513 Token endToken = element.endToken; | 616 Token endToken = element.endToken; |
| 514 Token token = beginToken; | 617 Token token = beginToken; |
| 515 if (element is PartialClassElement) { | 618 if (element is PartialClassElement) { |
| 516 ClassNode node = element.cachedNode; | 619 ClassNode node = element.cachedNode; |
| 517 if (node != null) { | 620 if (node != null) { |
| 518 NodeList body = node.body; | 621 NodeList body = node.body; |
| 519 if (body != null) { | 622 if (body != null) { |
| 520 endToken = body.beginToken; | 623 endToken = body.beginToken; |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 551 // of [element]. | 654 // of [element]. |
| 552 return true; | 655 return true; |
| 553 } | 656 } |
| 554 } | 657 } |
| 555 return false; | 658 return false; |
| 556 } | 659 } |
| 557 | 660 |
| 558 DeclarationSite declarationSite(Element element) { | 661 DeclarationSite declarationSite(Element element) { |
| 559 return element is ElementX ? element.declarationSite : null; | 662 return element is ElementX ? element.declarationSite : null; |
| 560 } | 663 } |
| 664 | |
| 665 abstract class JsFeatures { | |
| 666 Compiler get compiler; | |
| 667 | |
| 668 JavaScriptBackend get backend => compiler.backend; | |
| 669 | |
| 670 Namer get namer => backend.namer; | |
| 671 | |
| 672 CodeEmitterTask get emitter => backend.emitter; | |
| 673 } | |
| OLD | NEW |