OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
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. | |
4 | |
5 package com.google.dart.compiler.backend.js.analysis; | |
6 | |
7 import com.google.common.io.CharStreams; | |
8 import com.google.common.io.Closeables; | |
9 import com.google.dart.compiler.DartCompilerContext; | |
10 import com.google.dart.compiler.LibrarySource; | |
11 | |
12 import org.mozilla.javascript.EvaluatorException; | |
13 import org.mozilla.javascript.Parser; | |
14 import org.mozilla.javascript.ast.AstNode; | |
15 import org.mozilla.javascript.ast.AstRoot; | |
16 import org.mozilla.javascript.ast.NodeVisitor; | |
17 | |
18 import java.io.IOException; | |
19 import java.io.Reader; | |
20 import java.io.Writer; | |
21 import java.util.ArrayList; | |
22 import java.util.HashMap; | |
23 import java.util.LinkedHashSet; | |
24 import java.util.List; | |
25 import java.util.Map; | |
26 import java.util.Set; | |
27 | |
28 /** | |
29 * A JavaScript tree shaker that is specialized for the output produced by | |
30 * dartc. | |
31 */ | |
32 public class TreeShaker { | |
33 private static class VisitorIOException extends RuntimeException { | |
34 public VisitorIOException(IOException e) { | |
35 super(e); | |
36 } | |
37 | |
38 @Override | |
39 public IOException getCause() { | |
40 return (IOException) super.getCause(); | |
41 } | |
42 } | |
43 | |
44 private static final class OutputFileWriter implements NodeVisitor { | |
45 private final Set<AstNode> nodesToEmit; | |
46 private final Writer outputFile; | |
47 private final Reader inputFile; | |
48 private long lastReadPosition = 0; | |
49 private long outputSize = 0; | |
50 | |
51 private OutputFileWriter(Set<AstNode> nodesToEmit, Writer outputFile, Reader
inputFile) { | |
52 this.nodesToEmit = nodesToEmit; | |
53 this.outputFile = outputFile; | |
54 this.inputFile = inputFile; | |
55 } | |
56 | |
57 @Override | |
58 public boolean visit(AstNode node) { | |
59 if (node.getAstRoot() == node) { | |
60 return true; | |
61 } | |
62 | |
63 try { | |
64 if (nodesToEmit.contains(node)) { | |
65 int nodePosition = node.getAbsolutePosition(); | |
66 inputFile.skip(nodePosition - lastReadPosition); | |
67 | |
68 char[] buffer = new char[node.getLength()]; | |
69 int charsRead = inputFile.read(buffer); | |
70 assert (charsRead == buffer.length); | |
71 outputFile.write(buffer); | |
72 outputFile.write("\n"); | |
73 outputSize += charsRead + 1; | |
74 lastReadPosition = nodePosition + node.getLength(); | |
75 } | |
76 } catch (IOException e) { | |
77 throw new VisitorIOException(e); | |
78 } | |
79 | |
80 return false; | |
81 } | |
82 | |
83 public long getOutputSize() { | |
84 return outputSize; | |
85 } | |
86 } | |
87 | |
88 private static final boolean DEBUG = false; | |
89 | |
90 /** | |
91 * Returns the set of {@link AstNode}s that should be emitted into the final | |
92 * JS code. | |
93 */ | |
94 private static Set<AstNode> computeNodesToEmit(AstRoot root) { | |
95 List<AstNode> globals = new ArrayList<AstNode>(); | |
96 Map<String, List<JavascriptElement>> namesToElements = | |
97 new HashMap<String, List<JavascriptElement>>(); | |
98 TopLevelElementIndexer declVisitor = new TopLevelElementIndexer(namesToEleme
nts, globals); | |
99 root.visit(declVisitor); | |
100 | |
101 if (DEBUG) { | |
102 TopLevelElementIndexer.printNamesToElements(namesToElements); | |
103 TopLevelElementIndexer.printGlobals(globals); | |
104 } | |
105 | |
106 List<AstNode> worklist = new ArrayList<AstNode>(); | |
107 for (AstNode global : globals) { | |
108 worklist.add(global); | |
109 } | |
110 | |
111 worklist.addAll(declVisitor.getEntryPoints()); | |
112 DependencyComputer dependencyComputer = new DependencyComputer(namesToElemen
ts); | |
113 final Set<AstNode> nodesProcessed = new LinkedHashSet<AstNode>(); | |
114 while (!worklist.isEmpty()) { | |
115 AstNode node = worklist.remove(worklist.size() - 1); | |
116 if (!nodesProcessed.add(node)) { | |
117 continue; | |
118 } | |
119 | |
120 if (DEBUG) { | |
121 try { | |
122 System.out.println(node.toSource()); | |
123 System.out.println("Dependencies:"); | |
124 } catch (Exception e) { | |
125 // Ignore exceptions thrown by rhino's toSource method... | |
126 } | |
127 } | |
128 | |
129 List<JavascriptElement> dependencies = dependencyComputer.computeDependenc
ies(node); | |
130 for (JavascriptElement dependency : dependencies) { | |
131 if (dependency.isNative() || nodesProcessed.contains(dependency.getNode(
))) { | |
132 // Skip natives since they don't have a node in the AST | |
133 continue; | |
134 } | |
135 | |
136 if (DEBUG) { | |
137 System.out.println("\t" + dependency.getQualifiedName()); | |
138 } | |
139 | |
140 worklist.add(dependency.getNode()); | |
141 } | |
142 } | |
143 return nodesProcessed; | |
144 } | |
145 | |
146 /** | |
147 * Reduce the input JS file by following the conservative "call graph" and | |
148 * pruning dead code. | |
149 */ | |
150 public static long reduce(LibrarySource app, DartCompilerContext context, | |
151 String completeArtifactName, Writer outputFile) throws IOException { | |
152 Reader inputFile = context.getArtifactReader(app, "", completeArtifactName); | |
153 // Mark beyond the expected length so we can reset back to zero | |
154 AstRoot root = null; | |
155 boolean failed = true; | |
156 try { | |
157 Parser parser = new Parser(); | |
158 root = parser.parse(inputFile, "", 1); | |
159 failed = false; | |
160 } catch (EvaluatorException e) { | |
161 /* | |
162 * This can happen if we generate bad JS code. For example, the negative | |
163 * tests may cause invalid control flow constructs to be generated. In | |
164 * this case we will swallow the exception and simply copy the input file | |
165 * to the output file. | |
166 */ | |
167 Closeables.close(inputFile, failed); | |
168 inputFile = context.getArtifactReader(app, "", completeArtifactName); | |
169 return CharStreams.copy(inputFile, outputFile); | |
170 } finally { | |
171 Closeables.close(inputFile, failed); | |
172 } | |
173 | |
174 final Set<AstNode> nodesProcessed = computeNodesToEmit(root); | |
175 | |
176 // Need to get a new reader since we don't cache the stream | |
177 failed = true; | |
178 inputFile = context.getArtifactReader(app, "", completeArtifactName); | |
179 OutputFileWriter outputFileWriter = | |
180 new OutputFileWriter(nodesProcessed, outputFile, inputFile); | |
181 try { | |
182 root.visit(outputFileWriter); | |
183 failed = false; | |
184 return outputFileWriter.getOutputSize(); | |
185 } catch (VisitorIOException e) { | |
186 throw e.getCause(); | |
187 } finally { | |
188 Closeables.close(inputFile, failed); | |
189 } | |
190 } | |
191 } | |
OLD | NEW |