/* * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.dev.jjs; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.linker.Artifact; import com.google.gwt.core.ext.linker.ArtifactSet; import com.google.gwt.core.ext.linker.CompilationMetricsArtifact; import com.google.gwt.core.ext.linker.EmittedArtifact; import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility; import com.google.gwt.core.ext.linker.ModuleMetricsArtifact; import com.google.gwt.core.ext.linker.PrecompilationMetricsArtifact; import com.google.gwt.core.ext.linker.StatementRanges; import com.google.gwt.core.ext.linker.SymbolData; import com.google.gwt.core.ext.linker.SyntheticArtifact; import com.google.gwt.core.ext.linker.impl.StandardSymbolData; import com.google.gwt.core.ext.soyc.SourceMapRecorder; import com.google.gwt.core.ext.soyc.coderef.DependencyGraphRecorder; import com.google.gwt.core.ext.soyc.coderef.EntityRecorder; import com.google.gwt.core.ext.soyc.impl.DependencyRecorder; import com.google.gwt.core.ext.soyc.impl.SizeMapRecorder; import com.google.gwt.core.ext.soyc.impl.SplitPointRecorder; import com.google.gwt.core.ext.soyc.impl.StoryRecorder; import com.google.gwt.core.linker.SoycReportLinker; import com.google.gwt.dev.CompilerContext; import com.google.gwt.dev.MinimalRebuildCache; import com.google.gwt.dev.Permutation; import com.google.gwt.dev.PrecompileTaskOptions; import com.google.gwt.dev.cfg.ConfigurationProperties; import com.google.gwt.dev.cfg.EntryMethodHolderGenerator; import com.google.gwt.dev.cfg.ModuleDef; import com.google.gwt.dev.cfg.PermutationProperties; import com.google.gwt.dev.javac.CompilationProblemReporter; import com.google.gwt.dev.javac.CompilationState; import com.google.gwt.dev.javac.StandardGeneratorContext; import com.google.gwt.dev.javac.typemodel.TypeOracle; import com.google.gwt.dev.jdt.RebindPermutationOracle; import com.google.gwt.dev.jjs.UnifiedAst.AST; import com.google.gwt.dev.jjs.ast.Context; import com.google.gwt.dev.jjs.ast.JBlock; import com.google.gwt.dev.jjs.ast.JCastOperation; import com.google.gwt.dev.jjs.ast.JClassLiteral; import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JDeclaredType; import com.google.gwt.dev.jjs.ast.JMethod; import com.google.gwt.dev.jjs.ast.JMethodBody; import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JTypeOracle.StandardTypes; import com.google.gwt.dev.jjs.ast.JVisitor; import com.google.gwt.dev.jjs.impl.ArrayNormalizer; import com.google.gwt.dev.jjs.impl.AssertionNormalizer; import com.google.gwt.dev.jjs.impl.AssertionRemover; import com.google.gwt.dev.jjs.impl.AstDumper; import com.google.gwt.dev.jjs.impl.CatchBlockNormalizer; import com.google.gwt.dev.jjs.impl.CompileTimeConstantsReplacer; import com.google.gwt.dev.jjs.impl.ComputeCastabilityInformation; import com.google.gwt.dev.jjs.impl.ComputeExhaustiveCastabilityInformation; import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer; import com.google.gwt.dev.jjs.impl.ControlFlowRecorder; import com.google.gwt.dev.jjs.impl.DeadCodeElimination; import com.google.gwt.dev.jjs.impl.DevirtualizeDefaultMethodForwarding; import com.google.gwt.dev.jjs.impl.Devirtualizer; import com.google.gwt.dev.jjs.impl.EnumNameObfuscator; import com.google.gwt.dev.jjs.impl.EnumOrdinalizer; import com.google.gwt.dev.jjs.impl.EqualityNormalizer; import com.google.gwt.dev.jjs.impl.Finalizer; import com.google.gwt.dev.jjs.impl.FixAssignmentsToUnboxOrCast; import com.google.gwt.dev.jjs.impl.FullOptimizerContext; import com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST; import com.google.gwt.dev.jjs.impl.HandleCrossFragmentReferences; import com.google.gwt.dev.jjs.impl.ImplementCastsAndTypeChecks; import com.google.gwt.dev.jjs.impl.ImplementClassLiteralsAsFields; import com.google.gwt.dev.jjs.impl.ImplementJsVarargs; import com.google.gwt.dev.jjs.impl.JavaAstVerifier; import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap; import com.google.gwt.dev.jjs.impl.JjsUtils; import com.google.gwt.dev.jjs.impl.JsAbstractTextTransformer; import com.google.gwt.dev.jjs.impl.JsFunctionClusterer; import com.google.gwt.dev.jjs.impl.JsInteropRestrictionChecker; import com.google.gwt.dev.jjs.impl.JsNoopTransformer; import com.google.gwt.dev.jjs.impl.JsTypeLinker; import com.google.gwt.dev.jjs.impl.JsniRestrictionChecker; import com.google.gwt.dev.jjs.impl.LongCastNormalizer; import com.google.gwt.dev.jjs.impl.LongEmulationNormalizer; import com.google.gwt.dev.jjs.impl.MakeCallsStatic; import com.google.gwt.dev.jjs.impl.MethodCallSpecializer; import com.google.gwt.dev.jjs.impl.MethodCallTightener; import com.google.gwt.dev.jjs.impl.MethodInliner; import com.google.gwt.dev.jjs.impl.OptimizerContext; import com.google.gwt.dev.jjs.impl.OptimizerStats; import com.google.gwt.dev.jjs.impl.PostOptimizationCompoundAssignmentNormalizer; import com.google.gwt.dev.jjs.impl.Pruner; import com.google.gwt.dev.jjs.impl.RecordRebinds; import com.google.gwt.dev.jjs.impl.RemoveEmptySuperCalls; import com.google.gwt.dev.jjs.impl.RemoveSpecializations; import com.google.gwt.dev.jjs.impl.ReplaceCallsToNativeJavaLangObjectOverrides; import com.google.gwt.dev.jjs.impl.ReplaceGetClassOverrides; import com.google.gwt.dev.jjs.impl.ResolvePermutationDependentValues; import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences; import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.ClosureUniqueIdTypeMapper; import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.IntTypeMapper; import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.StringTypeMapper; import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.TypeMapper; import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.TypeOrder; import com.google.gwt.dev.jjs.impl.RewriteConstructorCallsForUnboxedTypes; import com.google.gwt.dev.jjs.impl.SameParameterValueOptimizer; import com.google.gwt.dev.jjs.impl.SourceInfoCorrelator; import com.google.gwt.dev.jjs.impl.TypeCoercionNormalizer; import com.google.gwt.dev.jjs.impl.TypeReferencesRecorder; import com.google.gwt.dev.jjs.impl.TypeTightener; import com.google.gwt.dev.jjs.impl.UnifyAst; import com.google.gwt.dev.jjs.impl.codesplitter.CodeSplitter; import com.google.gwt.dev.jjs.impl.codesplitter.CodeSplitters; import com.google.gwt.dev.jjs.impl.codesplitter.MultipleDependencyGraphRecorder; import com.google.gwt.dev.jjs.impl.codesplitter.ReplaceRunAsyncs; import com.google.gwt.dev.jjs.impl.gflow.DataflowOptimizer; import com.google.gwt.dev.js.BaselineCoverageGatherer; import com.google.gwt.dev.js.CoverageInstrumentor; import com.google.gwt.dev.js.DuplicateClinitRemover; import com.google.gwt.dev.js.EvalFunctionsAtTopScope; import com.google.gwt.dev.js.FreshNameGenerator; import com.google.gwt.dev.js.JsBreakUpLargeVarStatements; import com.google.gwt.dev.js.JsDuplicateCaseFolder; import com.google.gwt.dev.js.JsDuplicateFunctionRemover; import com.google.gwt.dev.js.JsForceInliningChecker; import com.google.gwt.dev.js.JsIncrementalNamer; import com.google.gwt.dev.js.JsInliner; import com.google.gwt.dev.js.JsLiteralInterner; import com.google.gwt.dev.js.JsNamer.IllegalNameException; import com.google.gwt.dev.js.JsNamespaceChooser; import com.google.gwt.dev.js.JsNamespaceOption; import com.google.gwt.dev.js.JsNormalizer; import com.google.gwt.dev.js.JsObfuscateNamer; import com.google.gwt.dev.js.JsPrettyNamer; import com.google.gwt.dev.js.JsReportGenerationVisitor; import com.google.gwt.dev.js.JsStackEmulator; import com.google.gwt.dev.js.JsStaticEval; import com.google.gwt.dev.js.JsSymbolResolver; import com.google.gwt.dev.js.JsUnusedFunctionRemover; import com.google.gwt.dev.js.JsVerboseNamer; import com.google.gwt.dev.js.SizeBreakdown; import com.google.gwt.dev.js.ast.JavaScriptVerifier; import com.google.gwt.dev.js.ast.JsContext; import com.google.gwt.dev.js.ast.JsForIn; import com.google.gwt.dev.js.ast.JsFunction; import com.google.gwt.dev.js.ast.JsLabel; import com.google.gwt.dev.js.ast.JsLiteral; import com.google.gwt.dev.js.ast.JsName; import com.google.gwt.dev.js.ast.JsNameOf; import com.google.gwt.dev.js.ast.JsNameRef; import com.google.gwt.dev.js.ast.JsNode; import com.google.gwt.dev.js.ast.JsParameter; import com.google.gwt.dev.js.ast.JsProgram; import com.google.gwt.dev.js.ast.JsVars; import com.google.gwt.dev.js.ast.JsVisitor; import com.google.gwt.dev.util.DefaultTextOutput; import com.google.gwt.dev.util.Memory; import com.google.gwt.dev.util.Name.SourceName; import com.google.gwt.dev.util.Pair; import com.google.gwt.dev.util.Util; import com.google.gwt.dev.util.arg.OptionOptimize; import com.google.gwt.dev.util.log.speedtracer.CompilerEventType; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; import com.google.gwt.soyc.SoycDashboard; import com.google.gwt.soyc.io.ArtifactsOutputDirectory; import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap; import com.google.gwt.thirdparty.guava.common.collect.Iterables; import com.google.gwt.thirdparty.guava.common.collect.Lists; import com.google.gwt.thirdparty.guava.common.collect.Multimap; import com.google.gwt.thirdparty.guava.common.collect.Sets; import org.xml.sax.SAXException; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.zip.GZIPInputStream; import javax.xml.parsers.ParserConfigurationException; /** * A base for classes that compile Java <code>JProgram</code> representations into corresponding Js * source.<br /> * * Work is split between a precompile() stage which is only called once and compilePerms() stage * which is called once per permutation. This allow build systems the option of distributing and * parallelizing some of the work. */ public final class JavaToJavaScriptCompiler { /** * Ending optimization passes when the rate of change has reached this value results in gaining * nearly all of the impact while avoiding the long tail of costly but low-impact passes. */ private static final float EFFICIENT_CHANGE_RATE = 0.01f; /** * Continuing to apply optimizations till the rate of change reaches this value causes the AST to * reach a fixed point. */ private static final int FIXED_POINT_CHANGE_RATE = 0; /** * Limits the number of optimization passes against the possible danger of an AST that does not * converge. */ private static final int MAX_PASSES = 100; static { // Preload the internal compiler exception just in case we run out of memory?. InternalCompilerException.preload(); } private final CompilerContext compilerContext; private final TreeLogger logger; private final ModuleDef module; private final PrecompileTaskOptions options; private JsProgram jsProgram; private JProgram jprogram; public JavaToJavaScriptCompiler(TreeLogger logger, CompilerContext compilerContext) { this.logger = logger; this.compilerContext = compilerContext; this.module = compilerContext.getModule(); this.options = compilerContext.getOptions(); } public static UnifiedAst precompile(TreeLogger logger, CompilerContext compilerContext, PrecompilationContext precompilationContext) throws UnableToCompleteException { return new JavaToJavaScriptCompiler(logger, compilerContext).precompile(precompilationContext); } /** * Compiles a particular permutation. * * @param logger the logger to use * @param compilerContext shared read only compiler state * @param permutation the permutation to compile * @return the permutation result * @throws UnableToCompleteException if an error other than {@link OutOfMemoryError} occurs */ public static PermutationResult compilePermutation(UnifiedAst unifiedAst, TreeLogger logger, CompilerContext compilerContext, Permutation permutation) throws UnableToCompleteException { JavaToJavaScriptCompiler javaToJavaScriptCompiler = new JavaToJavaScriptCompiler(logger, compilerContext); return javaToJavaScriptCompiler.compilePermutation(permutation, unifiedAst); } /** * Takes as input an unresolved Java AST (a Java AST wherein all rebind result classes are * available and have not yet been pruned down to the set applicable for a particular permutation) * that was previously constructed by the Precompiler and from that constructs output Js source * code and related information. This Js source and related information is packaged into a * Permutation instance and then returned. * * Permutation compilation is INTENDED to progress as a series of stages: * * <pre> * 1. initialize local state * 2. transform unresolved Java AST to resolved Java AST * 3. normalize the resolved Java AST * 4. optimize the resolved Java AST * 5. construct the Js AST * 6. normalize the Js AST * 7. optimize the Js AST * 8. generate Js source * 9. construct and return a value * </pre> * * There are some other types of work here (mostly metrics and data gathering) which do not serve * the goal of output program construction. This work should really be moved into subclasses or * some sort of callback or plugin system so as not to visually pollute the real compile logic.<br * /> * * Significant amounts of visitors implementing the intended above stages are triggered here but * in the wrong order. They have been noted for future cleanup. */ private PermutationResult compilePermutation(Permutation permutation, UnifiedAst unifiedAst) throws UnableToCompleteException { Event jjsCompilePermutationEvent = SpeedTracerLogger.start( CompilerEventType.JJS_COMPILE_PERMUTATION, "name", permutation.getProperties().prettyPrint() ); /* * Do not introduce any new pass here unless it is logically a part of one of the 9 defined * stages and is physically located in that stage. */ long permStartMs = System.currentTimeMillis(); try { Event javaEvent = SpeedTracerLogger.start(CompilerEventType.PERMUTATION_JAVA); // (1) Initialize local state. long startTimeMs = System.currentTimeMillis(); PermutationProperties properties = permutation.getProperties(); int permutationId = permutation.getId(); AST ast = unifiedAst.getFreshAst(); jprogram = ast.getJProgram(); jsProgram = ast.getJsProgram(); Map<StandardSymbolData, JsName> symbolTable = new TreeMap<StandardSymbolData, JsName>(new SymbolData.ClassIdentComparator()); // TODO(stalcup): hide metrics gathering in a callback or subclass logger.log(TreeLogger.INFO, "Compiling permutation " + permutationId + "..."); // (2) Transform unresolved Java AST to resolved Java AST ResolvePermutationDependentValues .exec(jprogram, properties, permutation.getPropertyAndBindingInfos()); // TODO(stalcup): hide metrics gathering in a callback or subclass // This has to happen before optimizations because functions might // be optimized out; we want those marked as "not executed", not "not // instrumentable". Multimap<String, Integer> instrumentableLines = null; if (CoverageInstrumentor.isCoverageEnabled()) { instrumentableLines = BaselineCoverageGatherer.exec(jprogram); } // Record initial set of type->type references. // type->type references need to be collected in two phases, 1) before any process to the // AST has happened (to record for example reference to types declaring compile-time // constants) and 2) after all normalizations to collect synthetic references (e.g. to // record references to runtime classes like LongLib). maybeRecordReferencesAndControlFlow(false); // Rewrite calls to from boxed constructor types to specialized unboxed methods RewriteConstructorCallsForUnboxedTypes.exec(jprogram); // Replace compile time constants by their values. // TODO(rluble): eventually move to normizeSemantics. CompileTimeConstantsReplacer.exec(jprogram); // TODO(stalcup): move to after normalize. // (3) Optimize the resolved Java AST optimizeJava(); // TODO(stalcup): move to before optimize. // (4) Normalize the resolved Java AST TypeMapper<?> typeMapper = normalizeSemantics(); // TODO(stalcup): this stage shouldn't exist, move into optimize. postNormalizationOptimizeJava(); // Now that the AST has stopped mutating update with the final references. maybeRecordReferencesAndControlFlow(true); javaEvent.end(); Event javaScriptEvent = SpeedTracerLogger.start(CompilerEventType.PERMUTATION_JAVASCRIPT); // (5) Construct the Js AST Pair<? extends JavaToJavaScriptMap, Set<JsNode>> jjsMapAndInlineableFunctions = GenerateJavaScriptAST.exec(logger, jprogram, jsProgram, compilerContext, typeMapper, symbolTable, properties); JavaToJavaScriptMap jjsmap = jjsMapAndInlineableFunctions.getLeft(); // TODO(stalcup): hide metrics gathering in a callback or subclass if (CoverageInstrumentor.isCoverageEnabled()) { CoverageInstrumentor.exec(jprogram, jsProgram, jjsmap, instrumentableLines); } // (6) Normalize the Js AST JsNormalizer.exec(jsProgram); // TODO(stalcup): move to AST construction JsSymbolResolver.exec(jsProgram); if (options.getNamespace() == JsNamespaceOption.PACKAGE) { if (!jprogram.getRunAsyncs().isEmpty()) { options.setNamespace(JsNamespaceOption.NONE); logger.log(TreeLogger.Type.WARN, "Namespace option is not compatible with CodeSplitter, turning it off."); } else { JsNamespaceChooser.exec(jprogram, jsProgram, jjsmap); } } // TODO(stalcup): move to normalization Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder = splitJsIntoFragments(properties, permutationId, jjsmap); // TODO(stalcup): move to normalization EvalFunctionsAtTopScope.exec(jsProgram, jjsmap); // (7) Optimize the JS AST. final Set<JsNode> inlinableJsFunctions = jjsMapAndInlineableFunctions.getRight(); optimizeJs(inlinableJsFunctions); if (options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT) { JsForceInliningChecker.check(logger, jjsmap, jsProgram); } // TODO(stalcup): move to normalization // Must run before code splitter and namer. JsStackEmulator.exec(jprogram, jsProgram, properties, jjsmap); // TODO(stalcup): move to optimize. Map<JsName, JsLiteral> internedLiteralByVariableName = renameJsSymbols(properties, jjsmap); // No new JsNames or references to JSNames can be introduced after this // point. HandleCrossFragmentReferences.exec(jsProgram, properties); // TODO(stalcup): move to normalization JsBreakUpLargeVarStatements.exec(jsProgram, properties.getConfigurationProperties()); if (!options.isIncrementalCompileEnabled()) { // Verifies consistency between jsProgram and jjsmap if assertions are enabled. // TODO(rluble): make it work for incremental compiles. JavaScriptVerifier.verify(jsProgram, jjsmap); } // (8) Generate Js source List<JsSourceMap> sourceInfoMaps = new ArrayList<JsSourceMap>(); boolean isSourceMapsEnabled = properties.isTrueInAnyPermutation("compiler.useSourceMaps"); String[] jsFragments = new String[jsProgram.getFragmentCount()]; StatementRanges[] ranges = new StatementRanges[jsFragments.length]; SizeBreakdown[] sizeBreakdowns = options.isJsonSoycEnabled() || options.isSoycEnabled() || options.isCompilerMetricsEnabled() ? new SizeBreakdown[jsFragments.length] : null; generateJavaScriptCode(jjsmap, jsFragments, ranges, sizeBreakdowns, sourceInfoMaps, isSourceMapsEnabled || options.isJsonSoycEnabled()); javaScriptEvent.end(); // (9) Construct and return a value PermutationResult permutationResult = new PermutationResultImpl(jsFragments, permutation, makeSymbolMap(symbolTable), ranges); // TODO(stalcup): hide metrics gathering in a callback or subclass addSyntheticArtifacts(unifiedAst, permutation, startTimeMs, permutationId, jjsmap, dependenciesAndRecorder, internedLiteralByVariableName, isSourceMapsEnabled, jsFragments, sizeBreakdowns, sourceInfoMaps, permutationResult); return permutationResult; } catch (Throwable e) { throw CompilationProblemReporter.logAndTranslateException(logger, e); } finally { jjsCompilePermutationEvent.end(); if (logger.isLoggable(TreeLogger.TRACE)) { logger.log(TreeLogger.TRACE, "Permutation took " + (System.currentTimeMillis() - permStartMs) + " ms"); } } } private void maybeRecordReferencesAndControlFlow(boolean onlyUpdate) { if (options.isIncrementalCompileEnabled()) { // Per file compilation needs the type reference graph to construct the set of reachable // types when linking. TypeReferencesRecorder.exec(jprogram, getMinimalRebuildCache(), onlyUpdate); ControlFlowRecorder.exec(jprogram, getMinimalRebuildCache().getTypeEnvironment(), onlyUpdate); } } /** * Transform patterns that can't be represented in JS (such as multiple catch blocks) into * equivalent but compatible patterns and take JVM semantics (such as numeric casts) that are not * explicit in the AST and make them explicit.<br /> * * These passes can not be reordering because of subtle interdependencies. */ protected TypeMapper<?> normalizeSemantics() { Event event = SpeedTracerLogger.start(CompilerEventType.JAVA_NORMALIZERS); try { Devirtualizer.exec(jprogram); CatchBlockNormalizer.exec(jprogram); PostOptimizationCompoundAssignmentNormalizer.exec(jprogram); LongCastNormalizer.exec(jprogram); LongEmulationNormalizer.exec(jprogram); TypeCoercionNormalizer.exec(jprogram); if (options.isIncrementalCompileEnabled()) { // Per file compilation reuses type JS even as references (like casts) in other files // change, which means all legal casts need to be allowed now before they are actually // used later. ComputeExhaustiveCastabilityInformation.exec(jprogram); } else { // If trivial casts are pruned then one can use smaller runtime castmaps. ComputeCastabilityInformation.exec(jprogram, !shouldOptimize() /* recordTrivialCasts */); } ImplementCastsAndTypeChecks.exec(jprogram, shouldOptimize() /* pruneTrivialCasts */); ImplementJsVarargs.exec(jprogram); ArrayNormalizer.exec(jprogram); EqualityNormalizer.exec(jprogram); TypeMapper<?> typeMapper = getTypeMapper(); ResolveRuntimeTypeReferences.exec(jprogram, typeMapper, getTypeOrder()); return typeMapper; } finally { event.end(); } } private void optimizeJava() throws InterruptedException { if (shouldOptimize()) { optimizeJavaToFixedPoint(); RemoveEmptySuperCalls.exec(jprogram); } } private void optimizeJs(Set<JsNode> inlinableJsFunctions) throws InterruptedException { if (shouldOptimize()) { optimizeJsLoop(inlinableJsFunctions); JsDuplicateCaseFolder.exec(jsProgram); } } private void postNormalizationOptimizeJava() { Event event = SpeedTracerLogger.start(CompilerEventType.JAVA_POST_NORMALIZER_OPTIMIZERS); try { if (shouldOptimize()) { RemoveSpecializations.exec(jprogram); Pruner.exec(jprogram, false); // Last Java optimization step, update type oracle accordingly. jprogram.typeOracle.recomputeAfterOptimizations(jprogram.getDeclaredTypes()); } ReplaceGetClassOverrides.exec(jprogram); } finally { event.end(); } } private Map<JsName, JsLiteral> runDetailedNamer(ConfigurationProperties config) throws IllegalNameException { Map<JsName, JsLiteral> internedTextByVariableName = maybeInternLiterals(JsLiteralInterner.INTERN_ALL); JsVerboseNamer.exec(jsProgram, config); return internedTextByVariableName; } private Map<JsName, JsLiteral> maybeInternLiterals(int interningMask) { if (!shouldOptimize()) { return null; } // Only perform the interning optimization when optimizations are enabled. if (options.isClosureCompilerFormatEnabled()) { // Do no intern strings in closure format as it breaks goog.provides, etc. interningMask &= ~JsLiteralInterner.INTERN_STRINGS; } return JsLiteralInterner.exec(jprogram, jsProgram, interningMask); } private Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> splitJsIntoFragments( PermutationProperties properties, int permutationId, JavaToJavaScriptMap jjsmap) { Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder; MultipleDependencyGraphRecorder dependencyRecorder = null; SyntheticArtifact dependencies = null; if (options.isRunAsyncEnabled()) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int expectedFragmentCount = options.getFragmentCount(); // -1 is the default value, we trap 0 just in case (0 is not a legal value in any case) if (expectedFragmentCount <= 0) { // Fragment count not set check fragments merge. int numberOfMerges = options.getFragmentsMerge(); if (numberOfMerges > 0) { // + 1 for left over, + 1 for initial gave us the total number // of fragments without splitting. expectedFragmentCount = Math.max(0, jprogram.getRunAsyncs().size() + 2 - numberOfMerges); } } int minFragmentSize = properties.getConfigurationProperties() .getInteger(CodeSplitters.MIN_FRAGMENT_SIZE, 0); dependencyRecorder = chooseDependencyRecorder(baos); CodeSplitter.exec(logger, jprogram, jsProgram, jjsmap, expectedFragmentCount, minFragmentSize, dependencyRecorder); if (baos.size() == 0) { dependencyRecorder = recordNonSplitDependencies(baos); } if (baos.size() > 0) { dependencies = new SyntheticArtifact( SoycReportLinker.class, "dependencies" + permutationId + ".xml.gz", baos.toByteArray()); } } else if (options.isSoycEnabled() || options.isJsonSoycEnabled()) { dependencyRecorder = recordNonSplitDependencies(new ByteArrayOutputStream()); } dependenciesAndRecorder = Pair.create(dependencies, dependencyRecorder); return dependenciesAndRecorder; } private MultipleDependencyGraphRecorder chooseDependencyRecorder(OutputStream out) { MultipleDependencyGraphRecorder dependencyRecorder = MultipleDependencyGraphRecorder.NULL_RECORDER; if (options.isSoycEnabled() && options.isJsonSoycEnabled()) { dependencyRecorder = new DependencyGraphRecorder(out, jprogram); } else if (options.isSoycEnabled()) { dependencyRecorder = new DependencyRecorder(out); } else if (options.isJsonSoycEnabled()) { dependencyRecorder = new DependencyGraphRecorder(out, jprogram); } return dependencyRecorder; } /** * Dependency information is normally recorded during code splitting, and it results in multiple * dependency graphs. If the code splitter doesn't run, then this method can be used instead to * record a single dependency graph for the whole program. */ private DependencyRecorder recordNonSplitDependencies(OutputStream out) { DependencyRecorder deps; if (options.isSoycEnabled() && options.isJsonSoycEnabled()) { deps = new DependencyGraphRecorder(out, jprogram); } else if (options.isSoycEnabled()) { deps = new DependencyRecorder(out); } else if (options.isJsonSoycEnabled()) { deps = new DependencyGraphRecorder(out, jprogram); } else { return null; } deps.open(); deps.startDependencyGraph("initial", null); ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(jprogram); cfa.setDependencyRecorder(deps); cfa.traverseEntryMethods(); deps.endDependencyGraph(); deps.close(); return deps; } private CompilationMetricsArtifact addCompilerMetricsArtifact(UnifiedAst unifiedAst, Permutation permutation, long startTimeMs, SizeBreakdown[] sizeBreakdowns, PermutationResult permutationResult) { CompilationMetricsArtifact compilationMetrics = null; if (options.isCompilerMetricsEnabled()) { compilationMetrics = new CompilationMetricsArtifact(permutation.getId()); compilationMetrics.setCompileElapsedMilliseconds( System.currentTimeMillis() - startTimeMs); compilationMetrics.setElapsedMilliseconds( System.currentTimeMillis() - ManagementFactory.getRuntimeMXBean().getStartTime()); compilationMetrics.setJsSize(sizeBreakdowns); compilationMetrics.setPermutationDescription(permutation.getProperties().prettyPrint()); permutationResult.addArtifacts(Lists.newArrayList( unifiedAst.getModuleMetrics(), unifiedAst.getPrecompilationMetrics(), compilationMetrics)); } return compilationMetrics; } private void addSourceMapArtifacts(int permutationId, JavaToJavaScriptMap jjsmap, Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder, boolean isSourceMapsEnabled, SizeBreakdown[] sizeBreakdowns, List<JsSourceMap> sourceInfoMaps, PermutationResult permutationResult) { if (options.isJsonSoycEnabled()) { // Is a super set of SourceMapRecorder.makeSourceMapArtifacts(). permutationResult.addArtifacts(EntityRecorder.makeSoycArtifacts( permutationId, sourceInfoMaps, options.getSourceMapFilePrefix(), jjsmap, sizeBreakdowns, ((DependencyGraphRecorder) dependenciesAndRecorder.getRight()), jprogram)); } else if (isSourceMapsEnabled) { logger.log(TreeLogger.INFO, "Source Maps Enabled"); permutationResult.addArtifacts(SourceMapRecorder.exec(permutationId, sourceInfoMaps, options.getSourceMapFilePrefix())); } } /** * Adds generated artifacts from previous compiles when doing per-file compiles. <p> All * generators are run on first compile but only some very small subset are rerun on recompiles. * Care must be taken to ensure that all generated artifacts (such as png/html/css files) are * still registered for output even when no generators are run in the current compile. */ private void maybeAddGeneratedArtifacts(PermutationResult permutationResult) { if (options.isIncrementalCompileEnabled()) { permutationResult.addArtifacts( compilerContext.getMinimalRebuildCache().getGeneratedArtifacts()); } } private void addSoycArtifacts(UnifiedAst unifiedAst, int permutationId, JavaToJavaScriptMap jjsmap, Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder, Map<JsName, JsLiteral> internedLiteralByVariableName, String[] js, SizeBreakdown[] sizeBreakdowns, List<JsSourceMap> sourceInfoMaps, PermutationResult permutationResult, CompilationMetricsArtifact compilationMetrics) throws IOException, UnableToCompleteException { permutationResult.addArtifacts(makeSoycArtifacts(permutationId, js, sizeBreakdowns, options.isSoycExtra() ? sourceInfoMaps : null, dependenciesAndRecorder.getLeft(), jjsmap, internedLiteralByVariableName, unifiedAst.getModuleMetrics(), unifiedAst.getPrecompilationMetrics(), compilationMetrics, options.isSoycHtmlDisabled())); } private void addSyntheticArtifacts(UnifiedAst unifiedAst, Permutation permutation, long startTimeMs, int permutationId, JavaToJavaScriptMap jjsmap, Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder, Map<JsName, JsLiteral> internedLiteralByVariableName, boolean isSourceMapsEnabled, String[] jsFragments, SizeBreakdown[] sizeBreakdowns, List<JsSourceMap> sourceInfoMaps, PermutationResult permutationResult) throws IOException, UnableToCompleteException { assert internedLiteralByVariableName != null; Event event = SpeedTracerLogger.start(CompilerEventType.PERMUTATION_ARTIFACTS); CompilationMetricsArtifact compilationMetrics = addCompilerMetricsArtifact( unifiedAst, permutation, startTimeMs, sizeBreakdowns, permutationResult); addSoycArtifacts(unifiedAst, permutationId, jjsmap, dependenciesAndRecorder, internedLiteralByVariableName, jsFragments, sizeBreakdowns, sourceInfoMaps, permutationResult, compilationMetrics); addSourceMapArtifacts(permutationId, jjsmap, dependenciesAndRecorder, isSourceMapsEnabled, sizeBreakdowns, sourceInfoMaps, permutationResult); maybeAddGeneratedArtifacts(permutationResult); event.end(); } /** * Generate Js code from the given Js ASTs. Also produces information about that transformation. */ private void generateJavaScriptCode(JavaToJavaScriptMap jjsMap, String[] jsFragments, StatementRanges[] ranges, SizeBreakdown[] sizeBreakdowns, List<JsSourceMap> sourceInfoMaps, boolean sourceMapsEnabled) { Event generateJavascriptEvent = SpeedTracerLogger.start(CompilerEventType.GENERATE_JAVASCRIPT); for (int i = 0; i < jsFragments.length; i++) { DefaultTextOutput out = new DefaultTextOutput(!options.isIncrementalCompileEnabled() && options.getOutput().shouldMinimize()); JsReportGenerationVisitor v = new JsReportGenerationVisitor(out, jjsMap, options.isJsonSoycEnabled()); v.accept(jsProgram.getFragmentBlock(i)); StatementRanges statementRanges = v.getStatementRanges(); String code = out.toString(); JsSourceMap infoMap = (sourceInfoMaps != null) ? v.getSourceInfoMap() : null; JsAbstractTextTransformer transformer = new JsNoopTransformer(code, statementRanges, infoMap); /** * Cut generated JS up on class boundaries and re-link the source (possibly making use of * source from previous compiles, thus making it possible to perform partial recompiles). */ if (options.isIncrementalCompileEnabled()) { transformer = new JsTypeLinker(logger, transformer, v.getClassRanges(), v.getProgramClassRange(), getMinimalRebuildCache(), jprogram.typeOracle); transformer.exec(); } /** * Reorder function decls to improve compression ratios. Also restructures the top level * blocks into sub-blocks if they exceed 32767 statements. */ Event functionClusterEvent = SpeedTracerLogger.start(CompilerEventType.FUNCTION_CLUSTER); // TODO(cromwellian) move to the Js AST optimization, re-enable sourcemaps + clustering if (!sourceMapsEnabled && !options.isClosureCompilerFormatEnabled() && options.shouldClusterSimilarFunctions() && options.getNamespace() == JsNamespaceOption.NONE && options.getOutput() == JsOutputOption.OBFUSCATED) { transformer = new JsFunctionClusterer(transformer); transformer.exec(); } functionClusterEvent.end(); jsFragments[i] = transformer.getJs(); ranges[i] = transformer.getStatementRanges(); if (sizeBreakdowns != null) { sizeBreakdowns[i] = v.getSizeBreakdown(); } if (sourceInfoMaps != null) { sourceInfoMaps.add(transformer.getSourceInfoMap()); } } generateJavascriptEvent.end(); } private Collection<? extends Artifact<?>> makeSoycArtifacts(int permutationId, String[] js, SizeBreakdown[] sizeBreakdowns, List<JsSourceMap> sourceInfoMaps, SyntheticArtifact dependencies, JavaToJavaScriptMap jjsmap, Map<JsName, JsLiteral> internedLiteralByVariableName, ModuleMetricsArtifact moduleMetricsArtifact, PrecompilationMetricsArtifact precompilationMetricsArtifact, CompilationMetricsArtifact compilationMetrics, boolean htmlReportsDisabled) throws IOException, UnableToCompleteException { Memory.maybeDumpMemory("makeSoycArtifactsStart"); List<SyntheticArtifact> soycArtifacts = new ArrayList<SyntheticArtifact>(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); Event soycEvent = SpeedTracerLogger.start(CompilerEventType.MAKE_SOYC_ARTIFACTS); Event recordSplitPoints = SpeedTracerLogger.start( CompilerEventType.MAKE_SOYC_ARTIFACTS, "phase", "recordSplitPoints"); SplitPointRecorder.recordSplitPoints(jprogram, baos, logger); SyntheticArtifact splitPoints = new SyntheticArtifact( SoycReportLinker.class, "splitPoints" + permutationId + ".xml.gz", baos.toByteArray()); soycArtifacts.add(splitPoints); recordSplitPoints.end(); SyntheticArtifact sizeMaps = null; if (sizeBreakdowns != null) { Event recordSizeMap = SpeedTracerLogger.start( CompilerEventType.MAKE_SOYC_ARTIFACTS, "phase", "recordSizeMap"); baos.reset(); SizeMapRecorder.recordMap(logger, baos, sizeBreakdowns, jjsmap, internedLiteralByVariableName); sizeMaps = new SyntheticArtifact( SoycReportLinker.class, "stories" + permutationId + ".xml.gz", baos.toByteArray()); soycArtifacts.add(sizeMaps); recordSizeMap.end(); } if (sourceInfoMaps != null) { Event recordStories = SpeedTracerLogger.start( CompilerEventType.MAKE_SOYC_ARTIFACTS, "phase", "recordStories"); baos.reset(); StoryRecorder.recordStories(logger, baos, sourceInfoMaps, js); soycArtifacts.add(new SyntheticArtifact( SoycReportLinker.class, "detailedStories" + permutationId + ".xml.gz", baos.toByteArray())); recordStories.end(); } if (dependencies != null) { soycArtifacts.add(dependencies); } // Set all of the main SOYC artifacts private. for (SyntheticArtifact soycArtifact : soycArtifacts) { soycArtifact.setVisibility(Visibility.Private); } if (!htmlReportsDisabled && sizeBreakdowns != null) { Event generateCompileReport = SpeedTracerLogger.start( CompilerEventType.MAKE_SOYC_ARTIFACTS, "phase", "generateCompileReport"); ArtifactsOutputDirectory outDir = new ArtifactsOutputDirectory(); SoycDashboard dashboard = new SoycDashboard(outDir); dashboard.startNewPermutation(Integer.toString(permutationId)); try { dashboard.readSplitPoints(openWithGunzip(splitPoints)); if (sizeMaps != null) { dashboard.readSizeMaps(openWithGunzip(sizeMaps)); } if (dependencies != null) { dashboard.readDependencies(openWithGunzip(dependencies)); } Memory.maybeDumpMemory("soycReadDependenciesEnd"); } catch (ParserConfigurationException e) { throw new InternalCompilerException( "Error reading compile report information that was just generated", e); } catch (SAXException e) { throw new InternalCompilerException( "Error reading compile report information that was just generated", e); } dashboard.generateForOnePermutation(); if (moduleMetricsArtifact != null && precompilationMetricsArtifact != null && compilationMetrics != null) { dashboard.generateCompilerMetricsForOnePermutation( moduleMetricsArtifact, precompilationMetricsArtifact, compilationMetrics); } soycArtifacts.addAll(outDir.getArtifacts()); generateCompileReport.end(); } soycEvent.end(); return soycArtifacts; } private SymbolData[] makeSymbolMap(Map<StandardSymbolData, JsName> symbolTable) { // Keep tracks of a list of referenced name. If it is not used, don't // add it to symbol map. final Set<String> nameUsed = new HashSet<String>(); final Map<JsName, Integer> nameToFragment = new HashMap<JsName, Integer>(); for (int i = 0; i < jsProgram.getFragmentCount(); i++) { final Integer fragId = i; new JsVisitor() { @Override public void endVisit(JsForIn x, JsContext ctx) { if (x.getIterVarName() != null) { nameUsed.add(x.getIterVarName().getIdent()); } } @Override public void endVisit(JsFunction x, JsContext ctx) { if (x.getName() != null) { nameToFragment.put(x.getName(), fragId); nameUsed.add(x.getName().getIdent()); } } @Override public void endVisit(JsLabel x, JsContext ctx) { nameUsed.add(x.getName().getIdent()); } @Override public void endVisit(JsNameOf x, JsContext ctx) { if (x.getName() != null) { nameUsed.add(x.getName().getIdent()); } } @Override public void endVisit(JsNameRef x, JsContext ctx) { // Obviously this isn't even that accurate. Some of them are // variable names, some of the are property. At least this // this give us a safe approximation. Ideally we need // the code removal passes to remove stuff in the scope objects. if (x.isResolved()) { nameUsed.add(x.getName().getIdent()); } } @Override public void endVisit(JsParameter x, JsContext ctx) { nameUsed.add(x.getName().getIdent()); } @Override public void endVisit(JsVars.JsVar x, JsContext ctx) { nameUsed.add(x.getName().getIdent()); } }.accept(jsProgram.getFragmentBlock(i)); } // TODO(acleung): This is a temp fix. Once we know this is safe. We // new to rewrite it to avoid extra ArrayList creations. // Or we should just consider serializing it as an ArrayList if // it is that much trouble to determine the true size. List<SymbolData> result = new ArrayList<SymbolData>(); for (Map.Entry<StandardSymbolData, JsName> entry : symbolTable.entrySet()) { StandardSymbolData symbolData = entry.getKey(); symbolData.setSymbolName(entry.getValue().getShortIdent()); Integer fragNum = nameToFragment.get(entry.getValue()); if (fragNum != null) { symbolData.setFragmentNumber(fragNum); } if (nameUsed.contains(entry.getValue().getIdent()) || entry.getKey().isClass()) { result.add(symbolData); } } return result.toArray(new SymbolData[result.size()]); } /** * Open an emitted artifact and gunzip its contents. */ private InputStream openWithGunzip(EmittedArtifact artifact) throws IOException, UnableToCompleteException { return new BufferedInputStream(new GZIPInputStream(artifact.getContents(TreeLogger.NULL))); } private void optimizeJsLoop(Collection<JsNode> toInline) throws InterruptedException { int optimizationLevel = options.getOptimizationLevel(); List<OptimizerStats> allOptimizerStats = Lists.newArrayList(); int counter = 0; while (true) { counter++; if (Thread.interrupted()) { throw new InterruptedException(); } Event optimizeJsEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE_JS); OptimizerStats stats = new OptimizerStats("Pass " + counter); // Remove unused functions if possible. stats.add(JsStaticEval.exec(jsProgram)); // Inline Js function invocations stats.add(JsInliner.exec(jsProgram, toInline)); // Remove unused functions if possible. stats.add(JsUnusedFunctionRemover.exec(jsProgram)); // Save the stats to print out after optimizers finish. allOptimizerStats.add(stats); optimizeJsEvent.end(); if ((optimizationLevel < OptionOptimize.OPTIMIZE_LEVEL_MAX && counter > optimizationLevel) || !stats.didChange()) { break; } } if (optimizationLevel > OptionOptimize.OPTIMIZE_LEVEL_DRAFT) { DuplicateClinitRemover.exec(jsProgram); } } private Map<JsName, JsLiteral> renameJsSymbols(PermutationProperties properties, JavaToJavaScriptMap jjsmap) throws UnableToCompleteException { Map<JsName, JsLiteral> internedLiteralByVariableName = null; try { switch (options.getOutput()) { case OBFUSCATED: internedLiteralByVariableName = runObfuscateNamer(options, properties, jjsmap); break; case PRETTY: internedLiteralByVariableName = runPrettyNamer(options, properties, jjsmap); break; case DETAILED: internedLiteralByVariableName = runDetailedNamer(properties.getConfigurationProperties()); break; default: throw new InternalCompilerException("Unknown output mode"); } } catch (IllegalNameException e) { logger.log(TreeLogger.ERROR, e.getMessage(), e); throw new UnableToCompleteException(); } return internedLiteralByVariableName == null ? ImmutableMap.<JsName, JsLiteral>of() : internedLiteralByVariableName; } private Map<JsName, JsLiteral> runObfuscateNamer(JJSOptions options, PermutationProperties properties, JavaToJavaScriptMap jjsmap) throws IllegalNameException { if (options.isIncrementalCompileEnabled()) { runIncrementalNamer(options, properties.getConfigurationProperties(), jjsmap); return null; } Map<JsName, JsLiteral> internedLiteralByVariableName = maybeInternLiterals(JsLiteralInterner.INTERN_ALL); FreshNameGenerator freshNameGenerator = JsObfuscateNamer.exec(jsProgram, properties.getConfigurationProperties()); if (options.shouldRemoveDuplicateFunctions() && JsStackEmulator.getStackMode(properties) == JsStackEmulator.StackMode.STRIP) { JsDuplicateFunctionRemover.exec(jsProgram, freshNameGenerator); } return internedLiteralByVariableName; } private Map<JsName, JsLiteral> runPrettyNamer(JJSOptions options, PermutationProperties properties, JavaToJavaScriptMap jjsmap) throws IllegalNameException { if (options.isIncrementalCompileEnabled()) { runIncrementalNamer(options, properties.getConfigurationProperties(), jjsmap); return null; } // We don't intern strings in pretty mode to improve readability Map<JsName, JsLiteral> internedLiteralByVariableName = maybeInternLiterals(JsLiteralInterner.INTERN_ALL & ~JsLiteralInterner.INTERN_STRINGS); JsPrettyNamer.exec(jsProgram, properties.getConfigurationProperties()); return internedLiteralByVariableName; } private void runIncrementalNamer(JJSOptions options, ConfigurationProperties configurationProperties, JavaToJavaScriptMap jjsmap) throws IllegalNameException { JsIncrementalNamer.exec(jsProgram, configurationProperties, compilerContext.getMinimalRebuildCache().getPersistentPrettyNamerState(), jjsmap, options.getOutput() == JsOutputOption.OBFUSCATED); } /** * Takes as input a CompilationState and transforms that into a unified by not yet resolved Java * AST (a Java AST wherein cross-class references have been connected and all rebind result * classes are available and have not yet been pruned down to the set applicable for a particular * permutation). This AST is packaged into a UnifiedAst instance and then returned. * * Precompilation is INTENDED to progress as a series of stages: * * <pre> * 1. initialize local state * 2. assert preconditions * 3. construct and unify the unresolved Java AST * 4. normalize the unresolved Java AST // arguably should be removed * 5. optimize the unresolved Java AST // arguably should be removed * 6. construct and return a value * </pre> * * There are some other types of work here (mostly metrics and data gathering) which do not serve * the goal of output program construction. This work should really be moved into subclasses or * some sort of callback or plugin system so as not to visually pollute the real compile logic.<br * /> * * Significant amounts of visitors implementing the intended above stages are triggered here but * in the wrong order. They have been noted for future cleanup. */ private UnifiedAst precompile(PrecompilationContext precompilationContext) throws UnableToCompleteException { try { // (0) Assert preconditions if (precompilationContext.getEntryPoints().length + precompilationContext.getAdditionalRootTypes().length == 0) { throw new IllegalArgumentException("entry point(s) required"); } boolean singlePermutation = precompilationContext.getPermutations().length == 1; PrecompilationMetricsArtifact precompilationMetrics = precompilationContext.getPrecompilationMetricsArtifact(); /* * Do not introduce any new pass here unless it is logically a part of one of the 6 defined * stages and is physically located in that stage. */ // (1) Initialize local state jprogram = new JProgram(compilerContext.getMinimalRebuildCache()); // Synchronize JTypeOracle with compile optimization behavior. jprogram.typeOracle.setOptimize( options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT); jsProgram = new JsProgram(); // (2) Construct and unify the unresolved Java AST CompilationState compilationState = constructJavaAst(precompilationContext); // TODO(stalcup): hide metrics gathering in a callback or subclass JsniRestrictionChecker.exec(logger, jprogram); JsInteropRestrictionChecker.exec(logger, jprogram, getMinimalRebuildCache()); logTypeOracleMetrics(precompilationMetrics, compilationState); Memory.maybeDumpMemory("AstOnly"); AstDumper.maybeDumpAST(jprogram); // TODO(stalcup): is in wrong place, move to optimization stage ConfigurationProperties configurationProperties = new ConfigurationProperties(module); EnumNameObfuscator.exec(jprogram, logger, configurationProperties, options); // (3) Normalize the unresolved Java AST // Replace default methods by static implementations. DevirtualizeDefaultMethodForwarding.exec(jprogram); // Replace calls to native overrides of object methods. ReplaceCallsToNativeJavaLangObjectOverrides.exec(jprogram); FixAssignmentsToUnboxOrCast.exec(jprogram); if (options.isEnableAssertions()) { AssertionNormalizer.exec(jprogram); } else { AssertionRemover.exec(jprogram); } if (module != null && options.isRunAsyncEnabled()) { ReplaceRunAsyncs.exec(logger, jprogram); ConfigurationProperties config = new ConfigurationProperties(module); CodeSplitters.pickInitialLoadSequence(logger, jprogram, config); } ImplementClassLiteralsAsFields.exec(jprogram, shouldOptimize()); // TODO(stalcup): hide metrics gathering in a callback or subclass logAstTypeMetrics(precompilationMetrics); // (4) Construct and return a value. Event createUnifiedAstEvent = SpeedTracerLogger.start(CompilerEventType.CREATE_UNIFIED_AST); UnifiedAst result = new UnifiedAst( options, new AST(jprogram, jsProgram), singlePermutation, RecordRebinds.exec(jprogram)); createUnifiedAstEvent.end(); return result; } catch (Throwable e) { throw CompilationProblemReporter.logAndTranslateException(logger, e); } } /** * Creates (and returns the name for) a new class to serve as the container for the invocation of * registered entry point methods as part of module bootstrapping.<br /> * * The resulting class will be invoked during bootstrapping like FooEntryMethodHolder.init(). By * generating the class on the fly and naming it to match the current module, the resulting holder * class can work in both monolithic and separate compilation schemes. */ private String buildEntryMethodHolder(StandardGeneratorContext context, String[] entryPointTypeNames, Set<String> allRootTypes) throws UnableToCompleteException { // If there are no entry points. if (entryPointTypeNames.length == 0) { // Then there's no need to generate an EntryMethodHolder class to launch them. return null; } EntryMethodHolderGenerator entryMethodHolderGenerator = new EntryMethodHolderGenerator(); String entryMethodHolderTypeName = entryMethodHolderGenerator.generate(logger, context, module.getCanonicalName()); context.finish(logger); // Ensures that unification traverses and keeps the class. allRootTypes.add(entryMethodHolderTypeName); // Ensures that JProgram knows to index this class's methods so that later bootstrap // construction code is able to locate the FooEntryMethodHolder.init() function. jprogram.addIndexedTypeName(entryMethodHolderTypeName); return entryMethodHolderTypeName; } private CompilationState constructJavaAst(PrecompilationContext precompilationContext) throws UnableToCompleteException { RebindPermutationOracle rpo = precompilationContext.getRebindPermutationOracle(); CompilationState compilationState = rpo.getCompilationState(); Memory.maybeDumpMemory("CompStateBuilt"); recordJsoTypes(compilationState.getTypeOracle()); unifyJavaAst(precompilationContext); if (options.isSoycEnabled() || options.isJsonSoycEnabled()) { SourceInfoCorrelator.exec(jprogram); } // Free up memory. rpo.clear(); Set<String> deletedTypeNames = options.isIncrementalCompileEnabled() ? getMinimalRebuildCache().computeDeletedTypeNames() : Sets.<String>newHashSet(); jprogram.typeOracle.computeBeforeAST(StandardTypes.createFrom(jprogram), jprogram.getDeclaredTypes(), jprogram.getModuleDeclaredTypes(), deletedTypeNames); return compilationState; } /** * This method can be used to fetch the list of referenced class. * * This method is intended to support compiler metrics. */ private String[] getReferencedJavaClasses() { class ClassNameVisitor extends JVisitor { List<String> classNames = new ArrayList<String>(); @Override public boolean visit(JClassType x, Context ctx) { classNames.add(x.getName()); return true; } } ClassNameVisitor v = new ClassNameVisitor(); v.accept(jprogram); return v.classNames.toArray(new String[v.classNames.size()]); } private void logAstTypeMetrics(PrecompilationMetricsArtifact precompilationMetrics) { if (options.isCompilerMetricsEnabled()) { precompilationMetrics.setAstTypes(getReferencedJavaClasses()); } } private void logTypeOracleMetrics( PrecompilationMetricsArtifact precompilationMetrics, CompilationState compilationState) { if (precompilationMetrics != null) { List<String> finalTypeOracleTypes = Lists.newArrayList(); for (com.google.gwt.core.ext.typeinfo.JClassType type : compilationState.getTypeOracle().getTypes()) { finalTypeOracleTypes.add(type.getPackage().getName() + "." + type.getName()); } precompilationMetrics.setFinalTypeOracleTypes(finalTypeOracleTypes); } } private Set<String> computeRootTypes(String[] entryPointTypeNames, String[] additionalRootTypes, CompilationState compilationState) { Set<String> allRootTypes = Sets.newTreeSet(); Iterables.addAll(allRootTypes, compilationState.getQualifiedJsInteropRootTypesNames()); Collections.addAll(allRootTypes, entryPointTypeNames); Collections.addAll(allRootTypes, additionalRootTypes); allRootTypes.addAll(JProgram.CODEGEN_TYPES_SET); allRootTypes.addAll(jprogram.getTypeNamesToIndex()); /* * Add all SingleJsoImpl types that we know about. It's likely that the concrete types are * never explicitly referenced. */ TypeOracle typeOracle = compilationState.getTypeOracle(); for (com.google.gwt.core.ext.typeinfo.JClassType singleJsoIntf : typeOracle.getSingleJsoImplInterfaces()) { allRootTypes.add(typeOracle.getSingleJsoImpl(singleJsoIntf).getQualifiedSourceName()); } return allRootTypes; } private void recordJsoTypes(TypeOracle typeOracle) { if (!options.isIncrementalCompileEnabled()) { return; } // Add names of JSO subtypes. Set<String> jsoTypeNames = Sets.newHashSet(); for (com.google.gwt.dev.javac.typemodel.JClassType subtype : typeOracle.getJavaScriptObject().getSubtypes()) { jsoTypeNames.add(subtype.getQualifiedBinaryName()); } // Add names of interfaces that are always of a JSO (aka there are no non-JSO implementors). Set<String> singleJsoImplInterfaceNames = Sets.newHashSet(); for (com.google.gwt.core.ext.typeinfo.JClassType singleJsoImplInterface : typeOracle.getSingleJsoImplInterfaces()) { singleJsoImplInterfaceNames.add(singleJsoImplInterface.getQualifiedBinaryName()); } // Add names of interfaces that are only sometimes a JSO (aka there are both JSO and non-JSO // imlementors). Set<String> dualJsoImplInterfaceNames = Sets.newHashSet(); for (com.google.gwt.core.ext.typeinfo.JClassType dualJsoImplInterface : typeOracle.getDualJsoImplInterfaces()) { dualJsoImplInterfaceNames.add(dualJsoImplInterface.getQualifiedBinaryName()); } compilerContext.getMinimalRebuildCache().setJsoTypeNames(jsoTypeNames, singleJsoImplInterfaceNames, dualJsoImplInterfaceNames); } private void synthesizeEntryMethodHolderInit(UnifyAst unifyAst, String[] entryPointTypeNames, String entryMethodHolderTypeName) throws UnableToCompleteException { // Get type references. JDeclaredType entryMethodHolderType = unifyAst.findType(entryMethodHolderTypeName, unifyAst.getSourceNameBasedTypeLocator()); JDeclaredType gwtType = unifyAst.findType("com.google.gwt.core.client.GWT", unifyAst.getSourceNameBasedTypeLocator()); JDeclaredType entryPointType = unifyAst.findType("com.google.gwt.core.client.EntryPoint", unifyAst.getSourceNameBasedTypeLocator()); // Get method references. JMethod initMethod = entryMethodHolderType.findMethod("init()V", false); JMethod gwtCreateMethod = gwtType.findMethod("create(Ljava/lang/Class;)Ljava/lang/Object;", false); // Synthesize all onModuleLoad() calls. JBlock initMethodBlock = ((JMethodBody) initMethod.getBody()).getBlock(); SourceInfo origin = initMethodBlock.getSourceInfo().makeChild(); for (String entryPointTypeName : entryPointTypeNames) { // Get type and onModuleLoad function for the current entryPointTypeName. JDeclaredType specificEntryPointType = unifyAst.findType(entryPointTypeName, unifyAst.getSourceNameBasedTypeLocator()); if (specificEntryPointType == null) { logger.log(TreeLogger.ERROR, "Could not find module entry point class '" + entryPointTypeName + "'", null); throw new UnableToCompleteException(); } JMethod onModuleLoadMethod = entryPointType.findMethod("onModuleLoad()V", true); JMethod specificOnModuleLoadMethod = specificEntryPointType.findMethod("onModuleLoad()V", true); if (specificOnModuleLoadMethod != null && specificOnModuleLoadMethod.isStatic()) { // Synthesize a static invocation FooEntryPoint.onModuleLoad(); call. JMethodCall staticOnModuleLoadCall = new JMethodCall(origin, null, specificOnModuleLoadMethod); initMethodBlock.addStmt(staticOnModuleLoadCall.makeStatement()); } else { // Synthesize ((EntryPoint)GWT.create(FooEntryPoint.class)).onModuleLoad(); JClassLiteral entryPointTypeClassLiteral = new JClassLiteral(origin, specificEntryPointType); JMethodCall createInstanceCall = new JMethodCall(origin, null, gwtCreateMethod, entryPointTypeClassLiteral); JCastOperation castToEntryPoint = new JCastOperation(origin, entryPointType, createInstanceCall); JMethodCall instanceOnModuleLoadCall = new JMethodCall(origin, castToEntryPoint, onModuleLoadMethod); initMethodBlock.addStmt(instanceOnModuleLoadCall.makeStatement()); } } } private void unifyJavaAst(PrecompilationContext precompilationContext) throws UnableToCompleteException { Event event = SpeedTracerLogger.start(CompilerEventType.UNIFY_AST); RebindPermutationOracle rpo = precompilationContext.getRebindPermutationOracle(); String[] entryPointTypeNames = precompilationContext.getEntryPoints(); String[] additionalRootTypes = precompilationContext.getAdditionalRootTypes(); Set<String> allRootTypes = computeRootTypes(entryPointTypeNames, additionalRootTypes, rpo.getCompilationState()); String entryMethodHolderTypeName = buildEntryMethodHolder(rpo.getGeneratorContext(), entryPointTypeNames, allRootTypes); UnifyAst unifyAst = new UnifyAst(logger, compilerContext, jprogram, jsProgram, precompilationContext); // Makes JProgram aware of these types so they can be accessed via index. unifyAst.addRootTypes(allRootTypes); // Must synthesize entryPoint.onModuleLoad() calls because some EntryPoint classes are // private. if (entryMethodHolderTypeName != null) { // Only synthesize the init method in the EntryMethodHolder class, if there is an // EntryMethodHolder class. synthesizeEntryMethodHolderInit(unifyAst, entryPointTypeNames, entryMethodHolderTypeName); } if (entryMethodHolderTypeName != null) { // Only register the init method in the EntryMethodHolder class as an entry method, if there // is an EntryMethodHolder class. jprogram.addEntryMethod(jprogram.getIndexedMethod( SourceName.getShortClassName(entryMethodHolderTypeName) + ".init")); } unifyAst.exec(); event.end(); } private void optimizeJavaToFixedPoint() throws InterruptedException { Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE); List<OptimizerStats> allOptimizerStats = Lists.newArrayList(); int passCount = 0; int nodeCount = jprogram.getNodeCount(); int lastNodeCount; boolean atMaxLevel = options.getOptimizationLevel() == OptionOptimize.OPTIMIZE_LEVEL_MAX; int passLimit = atMaxLevel ? MAX_PASSES : options.getOptimizationLevel(); float minChangeRate = atMaxLevel ? FIXED_POINT_CHANGE_RATE : EFFICIENT_CHANGE_RATE; OptimizerContext optimizerCtx = new FullOptimizerContext(jprogram); while (true) { passCount++; if (passCount > passLimit) { break; } if (Thread.interrupted()) { optimizeEvent.end(); throw new InterruptedException(); } AstDumper.maybeDumpAST(jprogram); OptimizerStats stats = optimizeJavaOneTime("Pass " + passCount, nodeCount, optimizerCtx); allOptimizerStats.add(stats); lastNodeCount = nodeCount; nodeCount = jprogram.getNodeCount(); float nodeChangeRate = stats.getNumMods() / (float) lastNodeCount; float sizeChangeRate = (lastNodeCount - nodeCount) / (float) lastNodeCount; if (nodeChangeRate <= minChangeRate && sizeChangeRate <= minChangeRate) { break; } } if (options.shouldOptimizeDataflow()) { // Just run it once, because it is very time consuming allOptimizerStats.add(DataflowOptimizer.exec(jprogram)); } optimizeEvent.end(); } private boolean shouldOptimize() { return options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT; } private TypeMapper getTypeMapper() { // Used to stabilize output for DeltaJS if (JjsUtils.closureStyleLiteralsNeeded(this.options)) { return new ClosureUniqueIdTypeMapper(jprogram); } if (this.options.useDetailedTypeIds()) { return new StringTypeMapper(jprogram); } return this.options.isIncrementalCompileEnabled() ? compilerContext.getMinimalRebuildCache().getTypeMapper() : new IntTypeMapper(); } private TypeOrder getTypeOrder() { // Used to stabilize output for DeltaJS if (JjsUtils.closureStyleLiteralsNeeded(this.options)) { return TypeOrder.ALPHABETICAL; } if (this.options.useDetailedTypeIds()) { return TypeOrder.NONE; } return this.options.isIncrementalCompileEnabled() ? TypeOrder.ALPHABETICAL : TypeOrder.FREQUENCY; } private OptimizerStats optimizeJavaOneTime(String passName, int numNodes, OptimizerContext optimizerCtx) { Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "phase", "loop"); // Clinits might have become empty become empty. jprogram.typeOracle.recomputeAfterOptimizations(jprogram.getDeclaredTypes()); OptimizerStats stats = new OptimizerStats(passName); JavaAstVerifier.assertProgramIsConsistent(jprogram); stats.add(Pruner.exec(jprogram, true, optimizerCtx).recordVisits(numNodes)); stats.add(Finalizer.exec(jprogram, optimizerCtx).recordVisits(numNodes)); stats.add(MakeCallsStatic.exec(jprogram, options.shouldAddRuntimeChecks(), optimizerCtx) .recordVisits(numNodes)); stats.add(TypeTightener.exec(jprogram, optimizerCtx).recordVisits(numNodes)); stats.add(MethodCallTightener.exec(jprogram, optimizerCtx).recordVisits(numNodes)); // Note: Specialization should be done before inlining. stats.add(MethodCallSpecializer.exec(jprogram, optimizerCtx).recordVisits(numNodes)); stats.add(DeadCodeElimination.exec(jprogram, optimizerCtx).recordVisits(numNodes)); stats.add(MethodInliner.exec(jprogram, optimizerCtx).recordVisits(numNodes)); if (options.shouldInlineLiteralParameters()) { stats.add(SameParameterValueOptimizer.exec(jprogram, optimizerCtx).recordVisits(numNodes)); } if (options.shouldOrdinalizeEnums()) { stats.add(EnumOrdinalizer.exec(jprogram, optimizerCtx).recordVisits(numNodes)); } optimizeEvent.end(); return stats; } private MinimalRebuildCache getMinimalRebuildCache() { return compilerContext.getMinimalRebuildCache(); } private static class PermutationResultImpl implements PermutationResult { private final ArtifactSet artifacts = new ArtifactSet(); private final byte[][] js; private final String jsStrongName; private final Permutation permutation; private final byte[] serializedSymbolMap; private final StatementRanges[] statementRanges; public PermutationResultImpl(String[] jsFragments, Permutation permutation, SymbolData[] symbolMap, StatementRanges[] statementRanges) { byte[][] bytes = new byte[jsFragments.length][]; for (int i = 0; i < jsFragments.length; ++i) { bytes[i] = Util.getBytes(jsFragments[i]); } this.js = bytes; this.jsStrongName = Util.computeStrongName(bytes); this.permutation = permutation; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Util.writeObjectToStream(baos, (Object) symbolMap); this.serializedSymbolMap = baos.toByteArray(); } catch (IOException e) { throw new RuntimeException("Should never happen with in-memory stream", e); } this.statementRanges = statementRanges; } @Override public void addArtifacts(Collection<? extends Artifact<?>> newArtifacts) { this.artifacts.addAll(newArtifacts); } @Override public ArtifactSet getArtifacts() { return artifacts; } @Override public byte[][] getJs() { return js; } @Override public String getJsStrongName() { return jsStrongName; } @Override public Permutation getPermutation() { return permutation; } @Override public byte[] getSerializedSymbolMap() { return serializedSymbolMap; } @Override public StatementRanges[] getStatementRanges() { return statementRanges; } } }