All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.dev.jjs.impl.FragmentExtractor Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * 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.impl;

import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JConstructor;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JField;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.js.JsHoister.Cloner;
import com.google.gwt.dev.js.ast.JsBinaryOperation;
import com.google.gwt.dev.js.ast.JsBinaryOperator;
import com.google.gwt.dev.js.ast.JsContext;
import com.google.gwt.dev.js.ast.JsEmpty;
import com.google.gwt.dev.js.ast.JsExprStmt;
import com.google.gwt.dev.js.ast.JsExpression;
import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsInvocation;
import com.google.gwt.dev.js.ast.JsModVisitor;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsNumberLiteral;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsStatement;
import com.google.gwt.dev.js.ast.JsVars;
import com.google.gwt.dev.js.ast.JsVars.JsVar;
import com.google.gwt.dev.js.ast.JsVisitable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * A class that extracts a fragment of code based on a supplied liveness
 * condition.
 */
public class FragmentExtractor {
  /**
   * A {@link LivenessPredicate} that bases liveness on a single
   * {@link ControlFlowAnalyzer}.
   */
  public static class CfaLivenessPredicate implements LivenessPredicate {
    private final ControlFlowAnalyzer cfa;

    public CfaLivenessPredicate(ControlFlowAnalyzer cfa) {
      this.cfa = cfa;
    }

    public boolean isLive(JDeclaredType type) {
      return cfa.getInstantiatedTypes().contains(type);
    }

    public boolean isLive(JField field) {
      return cfa.getLiveFieldsAndMethods().contains(field)
          || cfa.getFieldsWritten().contains(field);
    }

    public boolean isLive(JMethod method) {
      return cfa.getLiveFieldsAndMethods().contains(method);
    }

    public boolean isLive(String string) {
      return cfa.getLiveStrings().contains(string);
    }

    public boolean miscellaneousStatementsAreLive() {
      return true;
    }
  }

  /**
   * 

* A predicate on whether statements and variables should be considered live. *

* * *

* Any supplied predicate must satisfy load-order dependencies. For any atom * considered live, the atoms it depends on at load time should also be live. * The following load-order dependencies exist: *

* *
    *
  • A class literal depends on the strings contained in its instantiation * instruction.
  • * *
  • Types depend on their supertype.
  • * *
  • Instance methods depend on their enclosing type.
  • * *
  • Static fields that are initialized to strings depend on the string they * are initialized to.
  • *
*/ public static interface LivenessPredicate { boolean isLive(JDeclaredType type); boolean isLive(JField field); boolean isLive(JMethod method); boolean isLive(String literal); /** * Whether miscellelaneous statements should be considered live. * Miscellaneous statements are any that the fragment extractor does not * recognize as being in any particular category. This method should almost * always return true, but does return false for * {@link NothingAlivePredicate}. */ boolean miscellaneousStatementsAreLive(); } /** * A {@link LivenessPredicate} where nothing is alive. */ public static class NothingAlivePredicate implements LivenessPredicate { public boolean isLive(JDeclaredType type) { return false; } public boolean isLive(JField field) { return false; } public boolean isLive(JMethod method) { return false; } public boolean isLive(String string) { return false; } public boolean miscellaneousStatementsAreLive() { return false; } } /** * A logger for statements that the fragment extractor encounters. Install one * using * {@link FragmentExtractor#setStatementLogger(com.google.gwt.fragserv.FragmentExtractor.StatementLogger)} * . */ public static interface StatementLogger { void logStatement(JsStatement stat, boolean isIncluded); } private static class NullStatementLogger implements StatementLogger { public void logStatement(JsStatement method, boolean isIncluded) { } } /** * Return the Java method corresponding to stat, or * null if there isn't one. It recognizes JavaScript of the form * function foo(...) { ...}, where foo is the name * of the JavaScript translation of a Java method. */ public static JMethod methodFor(JsStatement stat, JavaToJavaScriptMap map) { if (stat instanceof JsExprStmt) { JsExpression exp = ((JsExprStmt) stat).getExpression(); if (exp instanceof JsFunction) { JsFunction func = (JsFunction) exp; if (func.getName() != null) { JMethod method = map.nameToMethod(func.getName()); if (method != null) { return method; } } } } return map.vtableInitToMethod(stat); } private final JProgram jprogram; private final JsProgram jsprogram; private final JavaToJavaScriptMap map; private StatementLogger statementLogger = new NullStatementLogger(); public FragmentExtractor(JavaAndJavaScript javaAndJavaScript) { this(javaAndJavaScript.jprogram, javaAndJavaScript.jsprogram, javaAndJavaScript.map); } public FragmentExtractor(JProgram jprogram, JsProgram jsprogram, JavaToJavaScriptMap map) { this.jprogram = jprogram; this.jsprogram = jsprogram; this.map = map; } /** * Create a call to {@link AsyncFragmentLoader#onLoad}. */ public List createOnLoadedCall(int splitPoint) { JMethod loadMethod = jprogram.getIndexedMethod("AsyncFragmentLoader.onLoad"); JsName loadMethodName = map.nameForMethod(loadMethod); SourceInfo sourceInfo = jsprogram.getSourceInfo(); JsInvocation call = new JsInvocation(sourceInfo); call.setQualifier(wrapWithEntry(loadMethodName.makeRef(sourceInfo))); call.getArguments().add(new JsNumberLiteral(sourceInfo, splitPoint)); List newStats = Collections. singletonList(call.makeStmt()); return newStats; } /** * Assume that all code described by alreadyLoadedPredicate has * been downloaded. Extract enough JavaScript statements that the code * described by livenessPredicate can also run. The caller should * ensure that livenessPredicate includes strictly more live code * than alreadyLoadedPredicate. */ public List extractStatements(LivenessPredicate livenessPredicate, LivenessPredicate alreadyLoadedPredicate) { List extractedStats = new ArrayList(); /** * The type whose vtables can currently be installed. */ JClassType currentVtableType = null; JClassType pendingVtableType = null; JsExprStmt pendingDefineSeed = null; // Since we haven't run yet. assert jsprogram.getFragmentCount() == 1; List stats = jsprogram.getGlobalBlock().getStatements(); for (JsStatement stat : stats) { boolean keepIt; JClassType vtableTypeAssigned = vtableTypeAssigned(stat); if (vtableTypeAssigned != null && livenessPredicate.isLive(vtableTypeAssigned)) { boolean[] anyCtorsSetup = new boolean[1]; JsExprStmt result = maybeRemoveCtorsFromDefineSeedStmt(livenessPredicate, alreadyLoadedPredicate, stat, anyCtorsSetup); boolean anyWorkDone = anyCtorsSetup[0] || !alreadyLoadedPredicate.isLive(vtableTypeAssigned); if (anyWorkDone) { stat = result; keepIt = true; } else { pendingDefineSeed = result; pendingVtableType = vtableTypeAssigned; keepIt = false; } } else if (containsRemovableVars(stat)) { stat = removeSomeVars((JsVars) stat, livenessPredicate, alreadyLoadedPredicate); keepIt = !(stat instanceof JsEmpty); } else { keepIt = isLive(stat, livenessPredicate) && !isLive(stat, alreadyLoadedPredicate); } statementLogger.logStatement(stat, keepIt); if (keepIt) { if (vtableTypeAssigned != null) { currentVtableType = vtableTypeAssigned; } JClassType vtableType = vtableTypeNeeded(stat); if (vtableType != null && vtableType != currentVtableType) { assert pendingVtableType == vtableType; extractedStats.add(pendingDefineSeed); currentVtableType = pendingVtableType; pendingDefineSeed = null; pendingVtableType = null; } extractedStats.add(stat); } } return extractedStats; } /** * Find all Java methods that still exist in the resulting JavaScript, even * after JavaScript inlining and pruning. */ public Set findAllMethodsInJavaScript() { Set methodsInJs = new HashSet(); for (int frag = 0; frag < jsprogram.getFragmentCount(); frag++) { List stats = jsprogram.getFragmentBlock(frag).getStatements(); for (JsStatement stat : stats) { JMethod method = methodFor(stat); if (method != null) { methodsInJs.add(method); } } } return methodsInJs; } public void setStatementLogger(StatementLogger logger) { statementLogger = logger; } /** * Check whether this statement is a JsVars that contains * individual vars that could be removed. If it does, then * {@link #removeSomeVars(JsVars, LivenessPredicate, LivenessPredicate)} is * sensible for this statement and should be used instead of * {@link #isLive(JsStatement, com.google.gwt.fragserv.FragmentExtractor.LivenessPredicate)} * . */ private boolean containsRemovableVars(JsStatement stat) { if (stat instanceof JsVars) { for (JsVar var : (JsVars) stat) { JField field = map.nameToField(var.getName()); if (field != null) { return true; } } } return false; } private boolean isLive(JsStatement stat, LivenessPredicate livenessPredicate) { JClassType type = map.typeForStatement(stat); if (type != null) { // This is part of the code only needed once a type is instantiable return livenessPredicate.isLive(type); } JMethod meth = methodFor(stat); if (meth != null) { /* * This statement either defines a method or installs it in a vtable. */ if (!livenessPredicate.isLive(meth)) { // The method is not live. Skip it. return false; } // The method is live. Check that its enclosing type is instantiable. // TODO(spoon): this check should not be needed once the CFA is updated return !meth.needsVtable() || livenessPredicate.isLive(meth.getEnclosingType()); } return livenessPredicate.miscellaneousStatementsAreLive(); } /** * Check whether a variable is needed. If the variable is an intern variable, * then return whether the interned value is live. If the variable corresponds * to a Java field, then return whether the Java field is live. Otherwise, * assume the variable is needed and return true. * * Whenever this method is updated, also look at * {@link #containsRemovableVars(JsStatement)}. */ private boolean isLive(JsVar var, LivenessPredicate livenessPredicate) { JField field = map.nameToField(var.getName()); if (field != null) { // It's a field return livenessPredicate.isLive(field); } // It's not an intern variable at all return livenessPredicate.miscellaneousStatementsAreLive(); } /** * Weird case: the seed function's liveness is associated with the type * itself. However, individual constructors can have a liveness that is a * subset of the type's liveness. */ private JsExprStmt maybeRemoveCtorsFromDefineSeedStmt( final LivenessPredicate livenessPredicate, final LivenessPredicate alreadyLoadedPredicate, JsStatement stat, final boolean[] anyCtorsSetup) { Cloner c = new Cloner(); c.accept(((JsExprStmt) stat).getExpression()); JsExprStmt result = c.getExpression().makeStmt(); new JsModVisitor() { public void endVisit(JsNameRef x, JsContext ctx) { JMethod maybeCtor = map.nameToMethod(x.getName()); if (maybeCtor instanceof JConstructor) { JConstructor ctor = (JConstructor) maybeCtor; if (!livenessPredicate.isLive(ctor) || alreadyLoadedPredicate.isLive(ctor)) { ctx.removeMe(); } else { anyCtorsSetup[0] = true; } } }; /** * Overridden to allow insert/remove on the varargs portion. */ protected void doAcceptList(List collection) { doAcceptWithInsertRemove(collection); }; }.accept(result); return result; } /** * Return the Java method corresponding to stat, or * null if there isn't one. It recognizes JavaScript of the form * function foo(...) { ...}, where foo is the name * of the JavaScript translation of a Java method. */ private JMethod methodFor(JsStatement stat) { return methodFor(stat, map); } /** * If stat is a {@link JsVars} that initializes a bunch of intern vars, return * a modified statement that skips any vars are needed by * currentLivenessPredicate but not by * alreadyLoadedPredicate. */ private JsStatement removeSomeVars(JsVars stat, LivenessPredicate currentLivenessPredicate, LivenessPredicate alreadyLoadedPredicate) { JsVars newVars = new JsVars(stat.getSourceInfo()); for (JsVar var : stat) { if (isLive(var, currentLivenessPredicate) && !isLive(var, alreadyLoadedPredicate)) { newVars.add(var); } } if (newVars.getNumVars() == stat.getNumVars()) { // no change return stat; } if (newVars.iterator().hasNext()) { /* * The new variables are non-empty; return them. */ return newVars; } else { /* * An empty JsVars seems possibly surprising; return a true empty * statement instead. */ return new JsEmpty(stat.getSourceInfo()); } } /** * If state is of the form _ = String.prototype, * then return String. If the form is * defineSeed(id, superId, cTM, ctor1, ctor2, ...) return the type * corresponding to that id. Otherwise return null. */ private JClassType vtableTypeAssigned(JsStatement stat) { if (!(stat instanceof JsExprStmt)) { return null; } JsExprStmt expr = (JsExprStmt) stat; if (expr.getExpression() instanceof JsInvocation) { // Handle a defineSeed call. JsInvocation call = (JsInvocation) expr.getExpression(); if (!(call.getQualifier() instanceof JsNameRef)) { return null; } JsNameRef func = (JsNameRef) call.getQualifier(); if (func.getName() != jsprogram.getIndexedFunction("SeedUtil.defineSeed").getName()) { return null; } return map.typeForStatement(stat); } // Handle String. if (!(expr.getExpression() instanceof JsBinaryOperation)) { return null; } JsBinaryOperation binExpr = (JsBinaryOperation) expr.getExpression(); if (binExpr.getOperator() != JsBinaryOperator.ASG) { return null; } if (!(binExpr.getArg1() instanceof JsNameRef)) { return null; } JsNameRef lhs = (JsNameRef) binExpr.getArg1(); JsName underBar = jsprogram.getScope().findExistingName("_"); assert underBar != null; if (lhs.getName() != underBar) { return null; } if (!(binExpr.getArg2() instanceof JsNameRef)) { return null; } JsNameRef rhsRef = (JsNameRef) binExpr.getArg2(); if (!(rhsRef.getQualifier() instanceof JsNameRef)) { return null; } if (!((JsNameRef) rhsRef.getQualifier()).getShortIdent().equals("String")) { return null; } if (!rhsRef.getName().getShortIdent().equals("prototype")) { return null; } return map.typeForStatement(stat); } private JClassType vtableTypeNeeded(JsStatement stat) { JMethod meth = map.vtableInitToMethod(stat); if (meth != null) { if (meth.needsVtable()) { return (JClassType) meth.getEnclosingType(); } } return null; } /** * Wrap an expression with a call to $entry. */ private JsInvocation wrapWithEntry(JsExpression exp) { SourceInfo sourceInfo = exp.getSourceInfo(); JsInvocation call = new JsInvocation(sourceInfo); JsName entryFunctionName = jsprogram.getScope().findExistingName("$entry"); call.setQualifier(entryFunctionName.makeRef(sourceInfo)); call.getArguments().add(exp); return call; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy