 Chromium Code Reviews
 Chromium Code Reviews Issue 10542073:
  RFC: Resolution based tree-shaking.  (Closed) 
  Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge
    
  
    Issue 10542073:
  RFC: Resolution based tree-shaking.  (Closed) 
  Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge| Index: dart/lib/compiler/implementation/compiler.dart | 
| diff --git a/dart/lib/compiler/implementation/compiler.dart b/dart/lib/compiler/implementation/compiler.dart | 
| index 2b4679c73003466009e44e4fbf641bcc0d964b2b..b3f16992d4ebd9665157b472f113375c047ec38c 100644 | 
| --- a/dart/lib/compiler/implementation/compiler.dart | 
| +++ b/dart/lib/compiler/implementation/compiler.dart | 
| @@ -2,6 +2,13 @@ | 
| // 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. | 
| + | 
| +/** | 
| + * If true, print a warning for each method that was resolved, but not | 
| + * compiled. | 
| + */ | 
| +final bool REPORT_EXCESS_RESOLUTION = false; | 
| + | 
| class WorkItem { | 
| final Element element; | 
| TreeElements resolutionTree; | 
| @@ -15,8 +22,8 @@ class WorkItem { | 
| String run(Compiler compiler, Enqueuer world) { | 
| String code = world.universe.generatedCode[element]; | 
| if (code !== null) return code; | 
| - if (!isAnalyzed()) compiler.analyze(this); | 
| - return compiler.codegen(this); | 
| + resolutionTree = compiler.analyze(this, world); | 
| + return compiler.codegen(this, world); | 
| } | 
| } | 
| @@ -25,6 +32,13 @@ class Backend { | 
| Backend(this.compiler); | 
| + void enqueueAllTopLevelFunctions(LibraryElement lib, Enqueuer world) { | 
| + lib.forEachExport((Element e) { | 
| + if (e.isFunction()) world.addToWorkList(e); | 
| + }); | 
| + } | 
| + | 
| + abstract void enqueueHelpers(Enqueuer world); | 
| abstract String codegen(WorkItem work); | 
| abstract void processNativeClasses(world, libraries); | 
| abstract void assembleProgram(); | 
| @@ -49,6 +63,17 @@ class JavaScriptBackend extends Backend { | 
| generator = new SsaCodeGeneratorTask(this); | 
| } | 
| + void enqueueHelpers(Enqueuer world) { | 
| + enqueueAllTopLevelFunctions(compiler.jsHelperLibrary, world); | 
| + enqueueAllTopLevelFunctions(compiler.interceptorsLibrary, world); | 
| + for (var helper in [const SourceString('Closure'), | 
| + const SourceString('ConstantMap'), | 
| + const SourceString('ConstantProtoMap')]) { | 
| + var e = compiler.findHelper(helper); | 
| + if (e !== null) world.registerInstantiatedClass(e); | 
| + } | 
| + } | 
| + | 
| String codegen(WorkItem work) { | 
| HGraph graph = builder.build(work); | 
| optimizer.optimize(work, graph); | 
| @@ -205,6 +230,8 @@ class Compiler implements DiagnosticListener { | 
| void cancel([String reason, Node node, Token token, | 
| HInstruction instruction, Element element]) { | 
| + assembledCode = null; // Compilation failed. Make sure that we | 
| + // don't return a bogus result. | 
| SourceSpan span = const SourceSpan(null, null, null); | 
| if (node !== null) { | 
| span = spanFromNode(node); | 
| @@ -248,15 +275,25 @@ class Compiler implements DiagnosticListener { | 
| void enableNoSuchMethod(Element element) { | 
| // TODO(ahe): Move this method to Enqueuer. | 
| if (enabledNoSuchMethod) return; | 
| - if (element.enclosingElement == objectClass) return; | 
| + if (element.enclosingElement === objectClass) { | 
| + enqueuer.resolution.registerDynamicInvocationOf(element); | 
| + return; | 
| + } | 
| enabledNoSuchMethod = true; | 
| + enqueuer.resolution.registerInvocation(NO_SUCH_METHOD, | 
| + Selector.INVOCATION_2); | 
| enqueuer.codegen.registerInvocation(NO_SUCH_METHOD, | 
| - new Selector.invocation(2)); | 
| + Selector.INVOCATION_2); | 
| } | 
| void enableIsolateSupport(LibraryElement element) { | 
| // TODO(ahe): Move this method to Enqueuer. | 
| isolateLibrary = element; | 
| + enqueuer.resolution.addToWorkList(element.find(START_ROOT_ISOLATE)); | 
| + enqueuer.resolution.addToWorkList( | 
| + element.find(const SourceString('_currentIsolate'))); | 
| + enqueuer.resolution.addToWorkList( | 
| + element.find(const SourceString('_callInIsolate'))); | 
| enqueuer.codegen.addToWorkList(element.find(START_ROOT_ISOLATE)); | 
| } | 
| @@ -366,50 +403,118 @@ class Compiler implements DiagnosticListener { | 
| // should know this. | 
| world.populate(this, libraries.getValues()); | 
| - // Not yet ready to process the enqueuer.resolution queue... | 
| - // processQueue(enqueuer.resolution); | 
| + log('Resolving...'); | 
| + backend.enqueueHelpers(enqueuer.resolution); | 
| + processQueue(enqueuer.resolution, main); | 
| + log('Resolved ${enqueuer.resolution.resolvedElements.length} elements.'); | 
| + | 
| + log('Compiling...'); | 
| processQueue(enqueuer.codegen, main); | 
| + log('Compiled ${codegenWorld.generatedCode.length} methods.'); | 
| backend.assembleProgram(); | 
| - if (!enqueuer.codegen.queue.isEmpty()) { | 
| - internalErrorOnElement(enqueuer.codegen.queue.first().element, | 
| - "work list is not empty"); | 
| - } | 
| + | 
| + checkQueues(); | 
| } | 
| processQueue(Enqueuer world, Element main) { | 
| backend.processNativeClasses(world, libraries.getValues()); | 
| world.addToWorkList(main); | 
| codegenProgress.reset(); | 
| - while (!world.queue.isEmpty()) { | 
| - WorkItem work = world.queue.removeLast(); | 
| + world.forEach((WorkItem work) { | 
| withCurrentElement(work.element, () => work.run(this, world)); | 
| - } | 
| + }); | 
| world.queueIsClosed = true; | 
| assert(world.checkNoEnqueuedInvokedInstanceMethods()); | 
| world.registerFieldClosureInvocations(); | 
| } | 
| + /** | 
| + * Perform various checks of the queues. This includes checking that | 
| + * the queues are empty (nothing was added after we stopped | 
| + * processing the quese). Also compute the number of methods that | 
| 
ngeoffray
2012/06/12 12:44:14
quese -> queues
 
ahe
2012/06/12 18:47:13
Done.
 | 
| + * were resolved, but not compiled (aka excess resolution). | 
| + */ | 
| + checkQueues() { | 
| + for (var world in [enqueuer.resolution, enqueuer.codegen]) { | 
| + world.forEach((WorkItem work) { | 
| + internalErrorOnElement(work.element, "Work list is not empty."); | 
| + }); | 
| + } | 
| + var resolved = new Set.from(enqueuer.resolution.resolvedElements.getKeys()); | 
| + for (Element e in codegenWorld.generatedCode.getKeys()) { | 
| + resolved.remove(e); | 
| + } | 
| + for (Element e in new Set.from(resolved)) { | 
| + if (e.isClass() || | 
| + e.isField() || | 
| + e.isTypeVariable() || | 
| + e.isTypedef() || | 
| + e.kind === ElementKind.ABSTRACT_FIELD) { | 
| + resolved.remove(e); | 
| + } | 
| + if (e.kind === ElementKind.GENERATIVE_CONSTRUCTOR) { | 
| + if (e.enclosingElement.isInterface()) { | 
| + resolved.remove(e); | 
| + } | 
| + resolved.remove(e); | 
| + | 
| + } | 
| + if (e.getLibrary() === jsHelperLibrary) { | 
| + resolved.remove(e); | 
| + } | 
| + if (e.getLibrary() === interceptorsLibrary) { | 
| + resolved.remove(e); | 
| + } | 
| + } | 
| + log('Excess resolution work: ${resolved.length}.'); | 
| + if (!REPORT_EXCESS_RESOLUTION) return; | 
| + for (Element e in resolved) { | 
| + SourceSpan span = spanFromElement(e); | 
| + reportDiagnostic(span, 'Warning: $e resolved but not compiled.', false); | 
| + } | 
| + } | 
| + | 
| TreeElements analyzeElement(Element element) { | 
| + TreeElements elements = enqueuer.resolution.getCachedElements(element); | 
| + if (elements !== null) return elements; | 
| + if (element is AbstractFieldElement) { | 
| 
ngeoffray
2012/06/12 12:44:14
Maybe add this check together with the allowed che
 
ahe
2012/06/12 18:47:13
Done.
 | 
| + return null; | 
| + } | 
| + final int allowed = ElementCategory.VARIABLE | ElementCategory.FUNCTION | 
| + | ElementCategory.FACTORY; | 
| + if (!element.isAccessor() && (element.kind.category & allowed) == 0) { | 
| + return null; | 
| + } | 
| assert(parser !== null); | 
| Node tree = parser.parse(element); | 
| validator.validate(tree); | 
| unparseValidator.check(element); | 
| - TreeElements elements = resolver.resolve(element); | 
| + elements = resolver.resolve(element); | 
| checker.check(tree, elements); | 
| return elements; | 
| } | 
| - TreeElements analyze(WorkItem work) { | 
| - work.resolutionTree = analyzeElement(work.element); | 
| - return work.resolutionTree; | 
| + TreeElements analyze(WorkItem work, Enqueuer world) { | 
| + if (work.isAnalyzed()) return work.resolutionTree; | 
| + Element element = work.element; | 
| + TreeElements result = world.getCachedElements(element); | 
| + if (result !== null) return result; | 
| + if (world !== enqueuer.resolution) { | 
| + internalErrorOnElement(element, | 
| + 'Internal error: unresolved element: $element.'); | 
| + } | 
| + result = analyzeElement(element); | 
| + enqueuer.resolution.resolvedElements[element] = result; | 
| + return result; | 
| } | 
| - String codegen(WorkItem work) { | 
| + String codegen(WorkItem work, Enqueuer world) { | 
| + if (world !== enqueuer.codegen) return null; | 
| 
ngeoffray
2012/06/12 12:44:14
Can we actually prevent this situation? Or is that
 
ahe
2012/06/12 18:47:13
I'm not sure I understand your question.
 
ngeoffray
2012/06/13 07:31:57
It does not feel right that the resolver queue end
 
ahe
2012/06/13 08:20:41
I have some ideas for cleaning this up. If enqueue
 | 
| if (codegenProgress.elapsedInMs() > 500) { | 
| // TODO(ahe): Add structured diagnostics to the compiler API and | 
| // use it to separate this from the --verbose option. | 
| - log('compiled ${codegenWorld.generatedCode.length} methods'); | 
| + log('Compiled ${codegenWorld.generatedCode.length} methods.'); | 
| codegenProgress.reset(); | 
| } | 
| if (work.element.kind.category == ElementCategory.VARIABLE) { | 
| @@ -479,14 +584,16 @@ class Compiler implements DiagnosticListener { | 
| abstract void reportDiagnostic(SourceSpan span, String message, bool fatal); | 
| - SourceSpan spanFromTokens(Token begin, Token end) { | 
| + SourceSpan spanFromTokens(Token begin, Token end, [Uri uri]) { | 
| if (begin === null || end === null) { | 
| // TODO(ahe): We can almost always do better. Often it is only | 
| // end that is null. Otherwise, we probably know the current | 
| // URI. | 
| - throw 'cannot find tokens to produce error message'; | 
| + throw 'Cannot find tokens to produce error message.'; | 
| + } | 
| + if (uri === null) { | 
| + uri = currentElement.getCompilationUnit().script.uri; | 
| } | 
| - Uri uri = currentElement.getCompilationUnit().script.uri; | 
| return SourceSpan.withOffsets(begin, end, (beginOffset, endOffset) => | 
| new SourceSpan(uri, beginOffset, endOffset)); | 
| } | 
| @@ -509,11 +616,10 @@ class Compiler implements DiagnosticListener { | 
| element = currentElement; | 
| } | 
| Token position = element.position(); | 
| - if (position === null) { | 
| - Uri uri = element.getCompilationUnit().script.uri; | 
| - return new SourceSpan(uri, 0, 0); | 
| - } | 
| - return spanFromTokens(position, position); | 
| + Uri uri = element.getCompilationUnit().script.uri; | 
| + return (position === null) | 
| + ? new SourceSpan(uri, 0, 0) | 
| + : spanFromTokens(position, position, uri); | 
| } | 
| Script readScript(Uri uri, [ScriptTag node]) { |