Changes to allow the ClosureJsBackend to give people some options to play with:

- introduce a "fastDraft" mode that only runs the main code-removal passes and runs in about half the time and but produces code 2x the size (but still less than 10% of the original).
- allows it to run in an quasi-incremential mode by consuming the incrementially generated js (cuts ~4s off the build time but source maps are useless).
- allow source map generation to be turned off (saves a couple seconds).
- turns off not useful check passes and makes the rest switchable.

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@91 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
johnlenz@google.com 2011-10-06 06:41:19 +00:00
parent 30232ec2c0
commit b2918972e8
5 changed files with 174 additions and 102 deletions

View file

@ -13,6 +13,7 @@ import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Multiset;
import com.google.dart.compiler.CommandLineOptions.CompilerOptions;
import com.google.common.io.Closeables;
import com.google.dart.compiler.DartCompilerContext;
import com.google.dart.compiler.DartSource;
import com.google.dart.compiler.ast.DartClass;
@ -30,6 +31,8 @@ import com.google.dart.compiler.metrics.Tracer.TraceEvent;
import com.google.dart.compiler.resolver.ClassElement;
import com.google.dart.compiler.resolver.CoreTypeProvider;
import com.google.dart.compiler.resolver.MethodElement;
import com.google.dart.compiler.util.DefaultTextOutput;
import com.google.dart.compiler.util.TextOutput;
import java.io.IOException;
import java.io.Writer;
@ -51,6 +54,11 @@ import java.util.Set;
*/
public abstract class AbstractJsBackend extends AbstractBackend {
public static final String EXTENSION_JS = "js";
public static final String EXTENSION_APP_JS = "app.js";
public static final String EXTENSION_JS_SRC_MAP = "js.map";
public static final String EXTENSION_APP_JS_SRC_MAP = "app.js.map";
private static final String ROOT_PART_NAME = "";
private static final String STATICS_PART_NAME = "$statics$";
private static final String SEPARATOR_PART_NAME = "$seperator$";
@ -468,5 +476,74 @@ public abstract class AbstractJsBackend extends AbstractBackend {
return mangler.mangleEntryPoint(entry, context.getApplicationUnit().getElement());
}
@Override
public boolean isOutOfDate(DartSource src, DartCompilerContext context) {
return context.isOutOfDate(src, src, EXTENSION_JS);
}
@Override
public void compileUnit(DartUnit unit, DartSource src, DartCompilerContext context,
CoreTypeProvider typeProvider) throws IOException {
// Translate the AST to JS.
Map<String, JsProgram> parts = translateToJS(unit, context, typeProvider);
String srcName = src.getName();
for (Map.Entry<String, JsProgram> entry : parts.entrySet()) {
// Generate Javascript output.
TextOutput out = new DefaultTextOutput(false);
JsToStringGenerationVisitor srcGenerator;
String name = entry.getKey();
boolean failed = true;
Writer w;
JsProgram program = entry.getValue();
JsBlock globalBlock = program.getGlobalBlock();
TraceEvent srcEvent =
Tracer.canTrace() ? Tracer.start(DartEventType.JS_SOURCE_GEN, "src", srcName, "name",
name) : null;
try {
srcGenerator = new JsSourceGenerationVisitor(out);
// TODO(johnlenz): Make source maps optional.
srcGenerator.generateSourceMap(true);
srcGenerator.accept(globalBlock);
w = context.getArtifactWriter(src, name, EXTENSION_JS);
try {
w.write(out.toString());
failed = false;
} finally {
Closeables.close(w, failed);
}
} finally {
Tracer.end(srcEvent);
}
/*
* Currently, out of date checks require that we write a JS file even if it is empty.
* However, we should not write a map file if it is.
*/
if (!globalBlock.getStatements().isEmpty()) {
TraceEvent sourcemapEvent =
Tracer.canTrace() ? Tracer.start(DartEventType.WRITE_SOURCE_MAP, "src", srcName,
"name", name) : null;
try {
// Write out the source map.
w = context.getArtifactWriter(src, name, EXTENSION_JS_SRC_MAP);
failed = true;
try {
srcGenerator.writeSourceMap(w, src.getName());
failed = false;
} finally {
Closeables.close(w, failed);
}
} finally {
Tracer.end(sourcemapEvent);
}
}
}
}
protected abstract boolean shouldOptimize();
}

View file

@ -30,11 +30,14 @@ public class ClosureJsAst implements SourceAst {
private final Source source;
private final InputId inputId;
public ClosureJsAst(JsProgram program, String inputName, Source source) {
private final boolean validate;
public ClosureJsAst(JsProgram program, String inputName, Source source, boolean validate) {
assert(inputName != null);
this.program = program;
this.source = source;
this.inputId = new InputId(inputName);
this.validate = validate;
}
@Override
@ -71,6 +74,6 @@ public class ClosureJsAst implements SourceAst {
}
private void createAst(AbstractCompiler compiler) {
root = new ClosureJsAstTranslator().translate(program, inputId, source);
root = new ClosureJsAstTranslator(validate).translate(program, inputId, source);
}
}

View file

@ -78,6 +78,12 @@ public class ClosureJsAstTranslator {
private final Map<Source, StaticSourceFile> sourceCache =
new HashMap<Source, StaticSourceFile>();
private final boolean validate;
ClosureJsAstTranslator(boolean validate) {
this.validate = validate;
}
private StaticSourceFile getClosureSourceFile(Source source) {
StaticSourceFile closureSourceFile = sourceCache.get(source);
if (closureSourceFile == null) {
@ -97,7 +103,9 @@ public class ClosureJsAstTranslator {
script.addChildToBack(transform(s));
}
// Validate the structural integrity of the AST.
new AstValidator().validateScript(script);
if (validate) {
new AstValidator().validateScript(script);
}
return script;
}

View file

@ -12,6 +12,7 @@ import com.google.common.collect.Sets;
import com.google.common.io.CharStreams;
import com.google.common.io.Closeables;
import com.google.common.io.LimitInputStream;
import com.google.dart.compiler.CommandLineOptions;
import com.google.dart.compiler.DartCompilationError;
import com.google.dart.compiler.DartCompilerContext;
import com.google.dart.compiler.DartSource;
@ -20,10 +21,16 @@ import com.google.dart.compiler.Source;
import com.google.dart.compiler.ast.DartUnit;
import com.google.dart.compiler.ast.LibraryNode;
import com.google.dart.compiler.ast.LibraryUnit;
import com.google.dart.compiler.backend.js.ast.JsBlock;
import com.google.dart.compiler.backend.js.ast.JsProgram;
import com.google.dart.compiler.common.SourceInfo;
import com.google.dart.compiler.metrics.CompilerMetrics;
import com.google.dart.compiler.metrics.DartEventType;
import com.google.dart.compiler.metrics.Tracer;
import com.google.dart.compiler.metrics.Tracer.TraceEvent;
import com.google.dart.compiler.resolver.CoreTypeProvider;
import com.google.dart.compiler.util.DefaultTextOutput;
import com.google.dart.compiler.util.TextOutput;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.Compiler;
@ -33,8 +40,12 @@ import com.google.javascript.jscomp.DiagnosticGroups;
import com.google.javascript.jscomp.JSError;
import com.google.javascript.jscomp.JSModule;
import com.google.javascript.jscomp.JSSourceFile;
import com.google.javascript.jscomp.PropertyRenamingPolicy;
import com.google.javascript.jscomp.Result;
import com.google.javascript.jscomp.SourceAst;
import com.google.javascript.jscomp.SourceMap.DetailLevel;
import com.google.javascript.jscomp.SourceMap.Format;
import com.google.javascript.jscomp.VariableRenamingPolicy;
import com.google.javascript.jscomp.WarningLevel;
import java.io.IOException;
@ -58,8 +69,8 @@ import java.util.zip.ZipInputStream;
* @author johnlenz@google.com (John Lenz)
*/
public class ClosureJsBackend extends AbstractJsBackend {
public static final String EXTENSION_JS = "opt.js";
public static final String EXTENSION_JS_SRC_MAP = "opt.js.map";
private static final String EXTENSION_OPT_JS = "opt.js";
private static final String EXTENSION_OPT_JS_SRC_MAP = "opt.js.map";
// A map of possible input sources to use when building the optimized output.
private Map<String, DartUnit> dartSrcToUnitMap = Maps.newHashMap();
@ -67,27 +78,58 @@ public class ClosureJsBackend extends AbstractJsBackend {
// Generate "readable" output for debugging
private final boolean generateHumanReadableOutput;
// Generate "good" instead of "best" output.
private final boolean fastOutput;
// TODO(johnlenz): Currently we can only support incremential builds
// if we aren't building source maps.
private final boolean incremental;
private final boolean produceSourceMap;
// Validate the generated JavaScript
private final boolean validate;
public ClosureJsBackend() {
this.generateHumanReadableOutput = false;
this(false);
}
/**
* @param generateHumanReadableOutput - generates human readable javascript output.
*/
public ClosureJsBackend(boolean generateHumanReadableOutput) {
// Current default settings
this(false, true, false, true, generateHumanReadableOutput);
}
public ClosureJsBackend(boolean fastOutput,
boolean produceSourceMap,
boolean incremental,
boolean validate,
boolean generateHumanReadableOutput) {
this.fastOutput = fastOutput;
this.produceSourceMap = produceSourceMap;
// can't currently produce a valid source map incrementally
this.incremental = incremental && !produceSourceMap;
this.validate = validate;
this.generateHumanReadableOutput = generateHumanReadableOutput;
}
@Override
public boolean isOutOfDate(DartSource src, DartCompilerContext context) {
return true;
if (!incremental) {
return true;
} else {
return super.isOutOfDate(src, context);
}
}
@Override
public void compileUnit(DartUnit unit, DartSource src,
DartCompilerContext context, CoreTypeProvider typeProvider) {
dartSrcToUnitMap.put(src.getName(), unit);
DartCompilerContext context, CoreTypeProvider typeProvider) throws IOException {
if (!incremental) {
dartSrcToUnitMap.put(src.getName(), unit);
} else {
super.compileUnit(unit, src, context, typeProvider);
}
}
private Map<String, CompilerInput> createClosureJsAst(Map<String,JsProgram> parts, Source source) {
@ -98,7 +140,7 @@ public class ClosureJsBackend extends AbstractJsBackend {
for (Map.Entry<String,JsProgram> part : parts.entrySet()) {
String partName = part.getKey();
String inputName = name + ':' + partName;
SourceAst sourceAst = new ClosureJsAst(part.getValue(), inputName, source);
SourceAst sourceAst = new ClosureJsAst(part.getValue(), inputName, source, validate);
CompilerInput input = new CompilerInput(sourceAst, false);
translatedParts.put(part.getKey(), input);
}
@ -131,21 +173,38 @@ public class ClosureJsBackend extends AbstractJsBackend {
StringWriter w = new StringWriter();
Reader r = nativeSrc.getSourceReader();
CharStreams.copy(r, w);
inputs.add(new CompilerInput(JSSourceFile.fromCode(name, w.toString()), false));
inputs.add(new CompilerInput(createSource(name, w), false));
}
@Override
public void visitPart(Part part) {
public void visitPart(Part part) throws IOException {
DartSource src = part.unit.getSource();
Map<String, CompilerInput> translatedParts = translatedUnits.get(part.unit);
if (translatedParts == null) {
assert !sourcesByName.containsKey(src.getName());
sourcesByName.put(src.getName(), src);
Preconditions.checkNotNull(part.unit, "src: " + src.getName());
translatedParts = translateUnit(part.unit, src, context, typeProvider);
translatedUnits.put(part.unit, translatedParts);
if (!incremental) {
Map<String, CompilerInput> translatedParts = translatedUnits.get(part.unit);
if (translatedParts == null) {
assert !sourcesByName.containsKey(src.getName());
sourcesByName.put(src.getName(), src);
Preconditions.checkNotNull(part.unit, "src: " + src.getName());
translatedParts = translateUnit(part.unit, src, context, typeProvider);
Preconditions.checkState(!translatedUnits.containsKey(part.unit));
translatedUnits.put(part.unit, translatedParts);
}
inputs.add(translatedParts.get(part.part));
return;
}
inputs.add(translatedParts.get(part.part));
Reader r = context.getArtifactReader(src, part.part, EXTENSION_JS);
if (r == null) {
return;
}
StringWriter w = new StringWriter();
CharStreams.copy(r, w);
String inputName = src.getName() + ':' + part.part;
inputs.add(new CompilerInput(createSource(inputName, w), false));
}
private JSSourceFile createSource(String name, Writer w) {
return JSSourceFile.fromCode(name, w.toString());
}
}
@ -162,7 +221,7 @@ public class ClosureJsBackend extends AbstractJsBackend {
DependencyBuilder.build(context.getAppLibraryUnit(), callback);
// Lastly, add the entry point.
inputs.add( getCompilerInputForEntry(context) );
inputs.add(getCompilerInputForEntry(context));
// Currently, there is only a single module, add all the sources to it.
JSModule mainModule = new JSModule("main");
@ -172,10 +231,10 @@ public class ClosureJsBackend extends AbstractJsBackend {
}
}
Writer out = context.getArtifactWriter(app, "", EXTENSION_JS);
Writer out = context.getArtifactWriter(app, "", getAppExtension());
boolean failed = true;
try {
Writer srcMapOut = context.getArtifactWriter(app, "", EXTENSION_JS_SRC_MAP);
Writer srcMapOut = context.getArtifactWriter(app, "", getSourceMapExtension());
boolean failed2 = true;
try {
compileModule(
@ -318,16 +377,15 @@ public class ClosureJsBackend extends AbstractJsBackend {
Writer out, Writer srcMapOut)
throws IOException {
if (result.success) {
// TODO(johnlenz): Append directly to the writer.
String output = compiler.toSource(module);
out.append(output);
out.append('\n');
compiler.getSourceMap().appendTo(srcMapOut, module.getName());
if (produceSourceMap) {
compiler.getSourceMap().appendTo(srcMapOut, module.getName());
}
totalJsOutputCharCount = output.length();
// TODO(johnlenz): Output the externs declarations
// TODO(johnlenz): Output the manifest.
}
// return 0 if no errors, the error count otherwise

View file

@ -38,11 +38,6 @@ import java.util.Map;
*/
public class JavascriptBackend extends AbstractJsBackend {
public static final String EXTENSION_JS = "js";
public static final String EXTENSION_APP_JS = "app.js";
public static final String EXTENSION_JS_SRC_MAP = "js.map";
public static final String EXTENSION_APP_JS_SRC_MAP = "app.js.map";
/**
* Wraps an Appendable and keeps track of the current offset as line/columns.
*/
@ -180,75 +175,6 @@ public class JavascriptBackend extends AbstractJsBackend {
}
}
@Override
public boolean isOutOfDate(DartSource src, DartCompilerContext context) {
return context.isOutOfDate(src, src, EXTENSION_JS);
}
@Override
public void compileUnit(DartUnit unit, DartSource src, DartCompilerContext context,
CoreTypeProvider typeProvider) throws IOException {
// Translate the AST to JS.
Map<String, JsProgram> parts = translateToJS(unit, context, typeProvider);
String srcName = src.getName();
for (Map.Entry<String, JsProgram> entry : parts.entrySet()) {
// Generate Javascript output.
TextOutput out = new DefaultTextOutput(false);
JsToStringGenerationVisitor srcGenerator;
String name = entry.getKey();
boolean failed = true;
Writer w;
JsProgram program = entry.getValue();
JsBlock globalBlock = program.getGlobalBlock();
TraceEvent srcEvent =
Tracer.canTrace() ? Tracer.start(DartEventType.JS_SOURCE_GEN, "src", srcName, "name",
name) : null;
try {
srcGenerator = new JsSourceGenerationVisitor(out);
// TODO(johnlenz): Make source maps optional.
srcGenerator.generateSourceMap(true);
srcGenerator.accept(globalBlock);
w = context.getArtifactWriter(src, name, EXTENSION_JS);
try {
w.write(out.toString());
failed = false;
} finally {
Closeables.close(w, failed);
}
} finally {
Tracer.end(srcEvent);
}
/*
* Currently, out of date checks require that we write a JS file even if it is empty.
* However, we should not write a map file if it is.
*/
if (!globalBlock.getStatements().isEmpty()) {
TraceEvent sourcemapEvent =
Tracer.canTrace() ? Tracer.start(DartEventType.WRITE_SOURCE_MAP, "src", srcName,
"name", name) : null;
try {
// Write out the source map.
w = context.getArtifactWriter(src, name, EXTENSION_JS_SRC_MAP);
failed = true;
try {
srcGenerator.writeSourceMap(w, src.getName());
failed = false;
} finally {
Closeables.close(w, failed);
}
} finally {
Tracer.end(sourcemapEvent);
}
}
}
}
@Override
public void packageApp(LibrarySource app,
Collection<LibraryUnit> libraries,