| /* |
| * Copyright 2014 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.PrecompilationMetricsArtifact; |
| import com.google.gwt.core.ext.linker.SyntheticArtifact; |
| import com.google.gwt.core.ext.soyc.coderef.DependencyGraphRecorder; |
| import com.google.gwt.core.ext.soyc.impl.DependencyRecorder; |
| import com.google.gwt.core.linker.SoycReportLinker; |
| import com.google.gwt.dev.CompilerContext; |
| import com.google.gwt.dev.Permutation; |
| import com.google.gwt.dev.cfg.ConfigProps; |
| import com.google.gwt.dev.cfg.PermProps; |
| import com.google.gwt.dev.jdt.RebindPermutationOracle; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.impl.ArrayNormalizer; |
| import com.google.gwt.dev.jjs.impl.CatchBlockNormalizer; |
| import com.google.gwt.dev.jjs.impl.ComputeCastabilityInformation; |
| import com.google.gwt.dev.jjs.impl.ComputeExhaustiveCastabilityInformation; |
| import com.google.gwt.dev.jjs.impl.ComputeInstantiatedJsoInterfaces; |
| import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer; |
| import com.google.gwt.dev.jjs.impl.Devirtualizer; |
| import com.google.gwt.dev.jjs.impl.EqualityNormalizer; |
| import com.google.gwt.dev.jjs.impl.HandleCrossFragmentReferences; |
| import com.google.gwt.dev.jjs.impl.ImplementCastsAndTypeChecks; |
| import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap; |
| import com.google.gwt.dev.jjs.impl.JjsUtils; |
| import com.google.gwt.dev.jjs.impl.LongCastNormalizer; |
| import com.google.gwt.dev.jjs.impl.LongEmulationNormalizer; |
| import com.google.gwt.dev.jjs.impl.PostOptimizationCompoundAssignmentNormalizer; |
| import com.google.gwt.dev.jjs.impl.Pruner; |
| import com.google.gwt.dev.jjs.impl.RemoveEmptySuperCalls; |
| import com.google.gwt.dev.jjs.impl.RemoveSpecializations; |
| import com.google.gwt.dev.jjs.impl.ReplaceGetClassOverrides; |
| 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.TypeCoercionNormalizer; |
| 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.js.JsDuplicateCaseFolder; |
| import com.google.gwt.dev.js.JsLiteralInterner; |
| import com.google.gwt.dev.js.JsNamer.IllegalNameException; |
| import com.google.gwt.dev.js.JsVerboseNamer; |
| import com.google.gwt.dev.js.ast.JsLiteral; |
| import com.google.gwt.dev.js.ast.JsName; |
| import com.google.gwt.dev.js.ast.JsNode; |
| import com.google.gwt.dev.util.Pair; |
| 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 java.io.ByteArrayOutputStream; |
| import java.io.OutputStream; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Compiles the Java <code>JProgram</code> representation into its corresponding Js source. |
| */ |
| public class MonolithicJavaToJavaScriptCompiler extends JavaToJavaScriptCompiler { |
| |
| private class MonolithicPermutationCompiler extends PermutationCompiler { |
| |
| public MonolithicPermutationCompiler(Permutation permutation) { |
| super(permutation); |
| } |
| |
| @Override |
| 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, options.isCastCheckingDisabled(), |
| !shouldOptimize() /* recordTrivialCasts */); |
| } |
| |
| ComputeInstantiatedJsoInterfaces.exec(jprogram); |
| ImplementCastsAndTypeChecks.exec(jprogram, options.isCastCheckingDisabled(), |
| shouldOptimize() /* pruneTrivialCasts */); |
| ArrayNormalizer.exec(jprogram, options.isCastCheckingDisabled()); |
| EqualityNormalizer.exec(jprogram); |
| |
| TypeMapper<?> typeMapper = getTypeMapper(); |
| ResolveRuntimeTypeReferences.exec(jprogram, typeMapper, getTypeOrder()); |
| return typeMapper; |
| } finally { |
| event.end(); |
| } |
| } |
| |
| @Override |
| protected void optimizeJava() throws InterruptedException { |
| if (shouldOptimize()) { |
| optimizeJavaToFixedPoint(); |
| RemoveEmptySuperCalls.exec(jprogram); |
| } |
| } |
| |
| @Override |
| protected void optimizeJs(Set<JsNode> inlinableJsFunctions) throws InterruptedException { |
| if (shouldOptimize()) { |
| optimizeJsLoop(inlinableJsFunctions); |
| JsDuplicateCaseFolder.exec(jsProgram); |
| } |
| } |
| |
| @Override |
| protected void postNormalizationOptimizeJava() { |
| Event event = SpeedTracerLogger.start(CompilerEventType.JAVA_POST_NORMALIZER_OPTIMIZERS); |
| try { |
| if (shouldOptimize()) { |
| RemoveSpecializations.exec(jprogram); |
| Pruner.exec(jprogram, false); |
| } |
| ReplaceGetClassOverrides.exec(jprogram); |
| } finally { |
| event.end(); |
| } |
| } |
| |
| @Override |
| protected Map<JsName, JsLiteral> runDetailedNamer(ConfigProps config) |
| throws IllegalNameException { |
| Map<JsName, JsLiteral> internedTextByVariableName = null; |
| if (shouldOptimize()) { |
| // Only perform the interning optimization when optimizations are enabled. |
| internedTextByVariableName = |
| JsLiteralInterner.exec(jprogram, jsProgram, (byte) (JsLiteralInterner.INTERN_ALL |
| & (byte) (jprogram.typeOracle.isJsInteropEnabled() |
| ? ~JsLiteralInterner.INTERN_STRINGS : ~0))); |
| } |
| JsVerboseNamer.exec(jsProgram, config); |
| return internedTextByVariableName; |
| } |
| |
| @Override |
| protected Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> splitJsIntoFragments( |
| PermProps props, 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 = props.getConfigProps().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.<SyntheticArtifact, MultipleDependencyGraphRecorder> create( |
| dependencies, dependencyRecorder); |
| |
| // No new JsNames or references to JSNames can be introduced after this |
| // point. |
| HandleCrossFragmentReferences.exec(jsProgram, props); |
| |
| 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 class MonolithicPrecompiler extends Precompiler { |
| |
| public MonolithicPrecompiler(RebindPermutationOracle rpo, String[] entryPointTypeNames) { |
| super(rpo, entryPointTypeNames); |
| } |
| |
| @Override |
| protected void beforeUnifyAst(Set<String> allRootTypes) |
| throws UnableToCompleteException { |
| } |
| |
| @Override |
| protected void checkEntryPoints(String[] additionalRootTypes) { |
| if (entryPointTypeNames.length + additionalRootTypes.length == 0) { |
| throw new IllegalArgumentException("entry point(s) required"); |
| } |
| } |
| |
| @Override |
| protected void createJProgram(CompilerContext compilerContext) { |
| jprogram = new JProgram(compilerContext.getMinimalRebuildCache()); |
| } |
| } |
| |
| /** |
| * Constructs a JavaToJavaScriptCompiler with customizations for compiling independent libraries. |
| */ |
| public MonolithicJavaToJavaScriptCompiler(TreeLogger logger, CompilerContext compilerContext) { |
| super(logger, compilerContext); |
| } |
| |
| @Override |
| public PermutationResult compilePermutation(UnifiedAst unifiedAst, Permutation permutation) |
| throws UnableToCompleteException { |
| return new MonolithicPermutationCompiler(permutation).compilePermutation(unifiedAst); |
| } |
| |
| @Override |
| public UnifiedAst precompile(RebindPermutationOracle rpo, String[] entryPointTypeNames, |
| String[] additionalRootTypes, boolean singlePermutation, |
| PrecompilationMetricsArtifact precompilationMetrics) throws UnableToCompleteException { |
| return new MonolithicPrecompiler(rpo, entryPointTypeNames).precompile(additionalRootTypes, |
| singlePermutation, precompilationMetrics); |
| } |
| |
| 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(); |
| } |
| 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; |
| } |
| } |