Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(63)

Unified Diff: compiler/java/com/google/dart/compiler/backend/js/analysis/TreeShaker.java

Issue 8676041: JS tree shaking for incremental builds. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: DartCompilerWarmup doesn't write files ending in *.app.js* Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: compiler/java/com/google/dart/compiler/backend/js/analysis/TreeShaker.java
diff --git a/compiler/java/com/google/dart/compiler/backend/js/analysis/TreeShaker.java b/compiler/java/com/google/dart/compiler/backend/js/analysis/TreeShaker.java
new file mode 100644
index 0000000000000000000000000000000000000000..27a975224cb3add388e26d1f2e9f5d7e70429ae7
--- /dev/null
+++ b/compiler/java/com/google/dart/compiler/backend/js/analysis/TreeShaker.java
@@ -0,0 +1,191 @@
+// Copyright (c) 2011, 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.
+
+package com.google.dart.compiler.backend.js.analysis;
+
+import com.google.common.io.CharStreams;
+import com.google.common.io.Closeables;
+import com.google.dart.compiler.DartCompilerContext;
+import com.google.dart.compiler.LibrarySource;
+
+import org.mozilla.javascript.EvaluatorException;
+import org.mozilla.javascript.Parser;
+import org.mozilla.javascript.ast.AstNode;
+import org.mozilla.javascript.ast.AstRoot;
+import org.mozilla.javascript.ast.NodeVisitor;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A JavaScript tree shaker that is specialized for the output produced by
+ * dartc.
+ */
+public class TreeShaker {
+ private static class VisitorIOException extends RuntimeException {
+ public VisitorIOException(IOException e) {
+ super(e);
+ }
+
+ @Override
+ public IOException getCause() {
+ return (IOException) super.getCause();
+ }
+ }
+
+ private static final class OutputFileWriter implements NodeVisitor {
+ private final Set<AstNode> nodesToEmit;
+ private final Writer outputFile;
+ private final Reader inputFile;
+ private long lastReadPosition = 0;
+ private long outputSize = 0;
+
+ private OutputFileWriter(Set<AstNode> nodesToEmit, Writer outputFile, Reader inputFile) {
+ this.nodesToEmit = nodesToEmit;
+ this.outputFile = outputFile;
+ this.inputFile = inputFile;
+ }
+
+ @Override
+ public boolean visit(AstNode node) {
+ if (node.getAstRoot() == node) {
+ return true;
+ }
+
+ try {
+ if (nodesToEmit.contains(node)) {
+ int nodePosition = node.getAbsolutePosition();
+ inputFile.skip(nodePosition - lastReadPosition);
+
+ char[] buffer = new char[node.getLength()];
+ int charsRead = inputFile.read(buffer);
+ assert (charsRead == buffer.length);
+ outputFile.write(buffer);
+ outputFile.write("\n");
+ outputSize += charsRead + 1;
+ lastReadPosition = nodePosition + node.getLength();
+ }
+ } catch (IOException e) {
+ throw new VisitorIOException(e);
+ }
+
+ return false;
+ }
+
+ public long getOutputSize() {
+ return outputSize;
+ }
+ }
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * Returns the set of {@link AstNode}s that should be emitted into the final
+ * JS code.
+ */
+ private static Set<AstNode> computeNodesToEmit(AstRoot root) {
+ List<AstNode> globals = new ArrayList<AstNode>();
+ Map<String, List<JavascriptElement>> namesToElements =
+ new HashMap<String, List<JavascriptElement>>();
+ TopLevelElementIndexer declVisitor = new TopLevelElementIndexer(namesToElements, globals);
+ root.visit(declVisitor);
+
+ if (DEBUG) {
+ TopLevelElementIndexer.printNamesToElements(namesToElements);
+ TopLevelElementIndexer.printGlobals(globals);
+ }
+
+ List<AstNode> worklist = new ArrayList<AstNode>();
+ for (AstNode global : globals) {
+ worklist.add(global);
+ }
+
+ worklist.addAll(declVisitor.getEntryPoints());
+ DependencyComputer dependencyComputer = new DependencyComputer(namesToElements);
+ final Set<AstNode> nodesProcessed = new LinkedHashSet<AstNode>();
+ while (!worklist.isEmpty()) {
+ AstNode node = worklist.remove(worklist.size() - 1);
+ if (!nodesProcessed.add(node)) {
+ continue;
+ }
+
+ if (DEBUG) {
+ try {
+ System.out.println(node.toSource());
+ System.out.println("Dependencies:");
+ } catch (Exception e) {
+ // Ignore exceptions thrown by rhino's toSource method...
+ }
+ }
+
+ List<JavascriptElement> dependencies = dependencyComputer.computeDependencies(node);
+ for (JavascriptElement dependency : dependencies) {
+ if (dependency.isNative() || nodesProcessed.contains(dependency.getNode())) {
+ // Skip natives since they don't have a node in the AST
+ continue;
+ }
+
+ if (DEBUG) {
+ System.out.println("\t" + dependency.getQualifiedName());
+ }
+
+ worklist.add(dependency.getNode());
+ }
+ }
+ return nodesProcessed;
+ }
+
+ /**
+ * Reduce the input JS file by following the conservative "call graph" and
+ * pruning dead code.
+ */
+ public static long reduce(LibrarySource app, DartCompilerContext context,
+ String completeArtifactName, Writer outputFile) throws IOException {
+ Reader inputFile = context.getArtifactReader(app, "", completeArtifactName);
+ // Mark beyond the expected length so we can reset back to zero
+ AstRoot root = null;
+ boolean failed = true;
+ try {
+ Parser parser = new Parser();
+ root = parser.parse(inputFile, "", 1);
+ failed = false;
+ } catch (EvaluatorException e) {
+ /*
+ * This can happen if we generate bad JS code. For example, the negative
+ * tests may cause invalid control flow constructs to be generated. In
+ * this case we will swallow the exception and simply copy the input file
+ * to the output file.
+ */
+ Closeables.close(inputFile, failed);
+ inputFile = context.getArtifactReader(app, "", completeArtifactName);
+ return CharStreams.copy(inputFile, outputFile);
+ } finally {
+ Closeables.close(inputFile, failed);
+ }
+
+ final Set<AstNode> nodesProcessed = computeNodesToEmit(root);
+
+ // Need to get a new reader since we don't cache the stream
+ failed = true;
+ inputFile = context.getArtifactReader(app, "", completeArtifactName);
+ OutputFileWriter outputFileWriter =
+ new OutputFileWriter(nodesProcessed, outputFile, inputFile);
+ try {
+ root.visit(outputFileWriter);
+ failed = false;
+ return outputFileWriter.getOutputSize();
+ } catch (VisitorIOException e) {
+ throw e.getCause();
+ } finally {
+ Closeables.close(inputFile, failed);
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698