| /* |
| * Copyright 2013 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.impl.codesplitter; |
| |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.core.ext.UnableToCompleteException; |
| import com.google.gwt.dev.cfg.ConfigurationProperties; |
| import com.google.gwt.dev.jjs.ast.JArrayType; |
| import com.google.gwt.dev.jjs.ast.JExpression; |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JMethodCall; |
| import com.google.gwt.dev.jjs.ast.JNewArray; |
| import com.google.gwt.dev.jjs.ast.JNode; |
| import com.google.gwt.dev.jjs.ast.JNumericEntry; |
| import com.google.gwt.dev.jjs.ast.JPrimitiveType; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JRunAsync; |
| import com.google.gwt.dev.jjs.impl.JsniRefLookup; |
| import com.google.gwt.dev.util.JsniRef; |
| import com.google.gwt.dev.util.log.speedtracer.CompilerEventType; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; |
| import com.google.gwt.thirdparty.guava.common.base.Function; |
| import com.google.gwt.thirdparty.guava.common.base.Joiner; |
| import com.google.gwt.thirdparty.guava.common.collect.Collections2; |
| import com.google.gwt.thirdparty.guava.common.collect.LinkedListMultimap; |
| import com.google.gwt.thirdparty.guava.common.collect.Lists; |
| import com.google.gwt.thirdparty.guava.common.collect.Multimap; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| |
| /** |
| * Utility function related to code splitting. |
| */ |
| public class CodeSplitters { |
| /** |
| * Choose an initial load sequence of split points for the specified program. |
| * Do so by identifying split points whose code always load first, before any |
| * other split points. As a side effect, modifies |
| * {@link com.google.gwt.core.client.impl.AsyncFragmentLoader#initialLoadSequence} |
| * in the program being compiled. |
| * |
| * @throws UnableToCompleteException If the module specifies a bad load order |
| */ |
| public static void pickInitialLoadSequence(TreeLogger logger, |
| JProgram program, ConfigurationProperties config) throws UnableToCompleteException { |
| SpeedTracerLogger.Event codeSplitterEvent = |
| SpeedTracerLogger |
| .start(CompilerEventType.CODE_SPLITTER, "phase", "pickInitialLoadSequence"); |
| TreeLogger branch = |
| logger.branch(TreeLogger.TRACE, "Looking up initial load sequence for split points"); |
| LinkedHashSet<JRunAsync> asyncsInInitialLoadSequence = new LinkedHashSet<JRunAsync>(); |
| |
| List<String> initialSequence = config.getStrings(PROP_INITIAL_SEQUENCE); |
| for (String runAsyncReference : initialSequence) { |
| JRunAsync runAsync = findRunAsync(runAsyncReference, program, branch); |
| if (asyncsInInitialLoadSequence.contains(runAsync)) { |
| branch.log(TreeLogger.ERROR, "Split point specified more than once: " + runAsyncReference); |
| } |
| asyncsInInitialLoadSequence.add(runAsync); |
| } |
| |
| logInitialLoadSequence(logger, asyncsInInitialLoadSequence); |
| installInitialLoadSequenceField(program, asyncsInInitialLoadSequence); |
| program.setInitialAsyncSequence(asyncsInInitialLoadSequence); |
| codeSplitterEvent.end(); |
| } |
| |
| /** |
| * Find a split point as designated in the {@link #PROP_INITIAL_SEQUENCE} |
| * configuration property. |
| */ |
| public static JRunAsync findRunAsync(String refString, JProgram program, TreeLogger branch) |
| throws UnableToCompleteException { |
| SpeedTracerLogger.Event codeSplitterEvent = |
| SpeedTracerLogger.start(CompilerEventType.CODE_SPLITTER, "phase", "findRunAsync"); |
| Multimap<String, JRunAsync> splitPointsByRunAsyncName = |
| computeRunAsyncsByName(program.getRunAsyncs(), false); |
| |
| if (refString.startsWith("@")) { |
| JsniRef jsniRef = JsniRef.parse(refString); |
| if (jsniRef == null) { |
| branch.log(TreeLogger.ERROR, "Badly formatted JSNI reference in " + PROP_INITIAL_SEQUENCE |
| + ": " + refString); |
| throw new UnableToCompleteException(); |
| } |
| final String lookupErrorHolder[] = new String[1]; |
| JNode referent = |
| JsniRefLookup.findJsniRefTarget(jsniRef, program, new JsniRefLookup.ErrorReporter() { |
| @Override |
| public void reportError(String error) { |
| lookupErrorHolder[0] = error; |
| } |
| }); |
| if (referent == null) { |
| TreeLogger resolveLogger = |
| branch.branch(TreeLogger.ERROR, "Could not resolve JSNI reference: " + jsniRef); |
| resolveLogger.log(TreeLogger.ERROR, lookupErrorHolder[0]); |
| throw new UnableToCompleteException(); |
| } |
| |
| if (!(referent instanceof JMethod)) { |
| branch.log(TreeLogger.ERROR, "Not a method: " + referent); |
| throw new UnableToCompleteException(); |
| } |
| |
| JMethod method = (JMethod) referent; |
| String canonicalName = ReplaceRunAsyncs.getImplicitName(method); |
| Collection<JRunAsync> splitPoints = splitPointsByRunAsyncName.get(canonicalName); |
| if (splitPoints == null) { |
| branch.log(TreeLogger.ERROR, "Method does not enclose a runAsync call: " + jsniRef); |
| throw new UnableToCompleteException(); |
| } |
| if (splitPoints.size() > 1) { |
| branch.log(TreeLogger.ERROR, "Method includes multiple runAsync calls, " |
| + "so it's ambiguous which one is meant: " + jsniRef); |
| throw new UnableToCompleteException(); |
| } |
| |
| assert splitPoints.size() == 1; |
| return splitPoints.iterator().next(); |
| } |
| |
| // Assume it's a raw class name |
| Collection<JRunAsync> splitPoints = splitPointsByRunAsyncName.get(refString); |
| if (splitPoints == null || splitPoints.size() == 0) { |
| branch.log(TreeLogger.ERROR, "No runAsync call is labelled with class " + refString); |
| throw new UnableToCompleteException(); |
| } |
| if (splitPoints.size() > 1) { |
| branch.log(TreeLogger.ERROR, "More than one runAsync call is labelled with class " |
| + refString); |
| throw new UnableToCompleteException(); |
| } |
| assert splitPoints.size() == 1; |
| JRunAsync result = splitPoints.iterator().next(); |
| codeSplitterEvent.end(); |
| |
| return result; |
| } |
| |
| /** |
| * Returns the collection of asyncs as a collection of singleton collections containing one |
| * async each. |
| */ |
| static Collection<Collection<JRunAsync>> getListOfLists(Collection<JRunAsync> runAsyncs) { |
| return Collections2.transform(runAsyncs, new Function<JRunAsync, Collection<JRunAsync>>() { |
| @Override |
| public Collection<JRunAsync> apply(JRunAsync runAsync) { |
| return Lists.newArrayList(runAsync); |
| } |
| }); |
| } |
| |
| /** |
| * Returns the number of exclusive fragments from the expected number of fragments. The |
| * result is expectedFragmentCount - (initials + 1) - 1 (for leftovers). |
| * |
| */ |
| public static int getNumberOfExclusiveFragmentFromExpectedFragmentCount( |
| int numberOfInitialAsyncs, int expectedFragmentCount) { |
| return Math.max(0, expectedFragmentCount - (numberOfInitialAsyncs + 1) - 1); |
| } |
| |
| /** |
| * A Java property that causes the fragment map to be logged. |
| */ |
| static String PROP_LOG_FRAGMENT_MAP = "gwt.jjs.logFragmentMap"; |
| static final String PROP_INITIAL_SEQUENCE = "compiler.splitpoint.initial.sequence"; |
| public static final String MIN_FRAGMENT_SIZE = "compiler.splitpoint.leftovermerge.size"; |
| |
| private static void logInitialLoadSequence(TreeLogger logger, |
| LinkedHashSet<JRunAsync> initialLoadSequence) { |
| if (!logger.isLoggable(TreeLogger.TRACE)) { |
| return; |
| } |
| |
| StringBuilder message = new StringBuilder(); |
| message.append("Initial load sequence of split points: "); |
| if (initialLoadSequence.isEmpty()) { |
| message.append("(none)"); |
| } else { |
| Collection<Integer> runAsyncIds = Collections2.transform(initialLoadSequence, |
| new Function<JRunAsync, Integer>() { |
| @Override |
| public Integer apply(JRunAsync runAsync) { |
| return runAsync.getRunAsyncId(); |
| } |
| }); |
| message.append(Joiner.on(", ").join(runAsyncIds)); |
| } |
| |
| logger.log(TreeLogger.TRACE, message.toString()); |
| } |
| |
| /** |
| * Installs the initial load sequence into AsyncFragmentLoader.BROWSER_LOADER. |
| * The initializer looks like this: |
| * |
| * <pre> |
| * AsyncFragmentLoader BROWSER_LOADER = makeBrowserLoader(1, new int[]{}); |
| * </pre> |
| * |
| * The second argument (<code>new int[]</code>) gets replaced by an array |
| * corresponding to <code>initialLoadSequence</code>. |
| */ |
| private static void installInitialLoadSequenceField(JProgram program, |
| LinkedHashSet<JRunAsync> initialLoadSequence) { |
| // Arg 1 is initialized in the source as "new int[]{}". |
| JMethodCall call = ReplaceRunAsyncs.getBrowserLoaderConstructor(program); |
| JExpression arg1 = call.getArgs().get(1); |
| assert arg1 instanceof JNewArray; |
| JArrayType arrayType = program.getTypeArray(JPrimitiveType.INT); |
| assert ((JNewArray) arg1).getArrayType() == arrayType; |
| List<JExpression> initializers = new ArrayList<JExpression>(initialLoadSequence.size()); |
| |
| // RunAsyncFramentIndex will later be replaced by the fragment the async is in. |
| // TODO(rluble): this approach is not very clean, ideally the load sequence should be |
| // installed AFTER code splitting when the fragment ids are known; rather than inserting |
| // a placeholder in the AST and patching the ast later. |
| for (JRunAsync runAsync : initialLoadSequence) { |
| initializers.add(new JNumericEntry(call.getSourceInfo(), "RunAsyncFragmentIndex", |
| runAsync.getRunAsyncId())); |
| } |
| JNewArray newArray = |
| JNewArray.createArrayWithInitializers(arg1.getSourceInfo(), arrayType, |
| Lists.newArrayList(initializers)); |
| call.setArg(1, newArray); |
| } |
| |
| static Multimap<String, JRunAsync> computeRunAsyncsByName(Collection<JRunAsync> runAsyncs, |
| boolean onlyExplicitNames) { |
| Multimap<String, JRunAsync> runAsyncsByName = LinkedListMultimap.create(); |
| for (JRunAsync runAsync : runAsyncs) { |
| String name = runAsync.getName(); |
| if (name == null || (onlyExplicitNames && !runAsync.hasExplicitClassLiteral())) { |
| continue; |
| } |
| runAsyncsByName.put(name, runAsync); |
| } |
| return runAsyncsByName; |
| } |
| } |