com.google.gwt.dev.jjs.impl.codesplitter.CodeSplitters Maven / Gradle / Ivy
The newest version!
/*
* 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 asyncsInInitialLoadSequence = new LinkedHashSet();
List 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 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 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 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> getListOfLists(Collection runAsyncs) {
return Collections2.transform(runAsyncs, new Function>() {
@Override
public Collection 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 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 runAsyncIds = Collections2.transform(initialLoadSequence,
new Function() {
@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:
*
*
* AsyncFragmentLoader BROWSER_LOADER = makeBrowserLoader(1, new int[]{});
*
*
* The second argument (new int[]
) gets replaced by an array
* corresponding to initialLoadSequence
.
*/
private static void installInitialLoadSequenceField(JProgram program,
LinkedHashSet 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 initializers = new ArrayList(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.createInitializers(arg1.getSourceInfo(), arrayType,
Lists.newArrayList(initializers));
call.setArg(1, newArray);
}
static Multimap computeRunAsyncsByName(Collection runAsyncs,
boolean onlyExplicitNames) {
Multimap runAsyncsByName = LinkedListMultimap.create();
for (JRunAsync runAsync : runAsyncs) {
String name = runAsync.getName();
if (name == null || (onlyExplicitNames && !runAsync.hasExplicitClassLiteral())) {
continue;
}
runAsyncsByName.put(name, runAsync);
}
return runAsyncsByName;
}
}