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

com.google.gwt.dev.jjs.impl.codesplitter.ExclusivityMap 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.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.JClassLiteral;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JField;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JNode;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JRunAsync;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JVisitor;
import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer;
import com.google.gwt.dev.js.ast.JsStatement;
import com.google.gwt.thirdparty.guava.common.base.Predicates;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Sets;

import java.util.ArrayDeque;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

/**
 * A map from program atoms to fragments; each fragment may contain more than one runAsync.
 * Maps atom to the fragments, if any, that they are exclusive to. Atoms not
 * exclusive to any fragment are either mapped to NOT_EXCLUSIVE, or left out of the map entirely.
 * Note that the map is incomplete; any entry not included has not been proven to be exclusive.
 * Also, note that the initial load sequence is assumed to already be loaded.
 */
class ExclusivityMap {
  /**
   * A liveness predicate that is based on an exclusivity map.
   */
  private class ExclusivityMapLivenessPredicate implements LivenessPredicate {
    private final Fragment fragment;

    public ExclusivityMapLivenessPredicate(Fragment fragment) {
      this.fragment = fragment;
    }

    @Override
    public boolean isLive(JDeclaredType type) {
      return isLiveInFragment(fragment, type);
    }

    @Override
    public boolean isLive(JField field) {
      return isLiveInFragment(fragment, field);
    }

    @Override
    public boolean isLive(JMethod method) {
      return isLiveInFragment(fragment, method);
    }

    @Override
    public boolean miscellaneousStatementsAreLive() {
      return true;
    }
  }

  /**
   * A dummy fragment that represents atoms that are not in the map.
   */
  public static final Fragment NOT_EXCLUSIVE = new Fragment(Fragment.Type.NOT_EXCLUSIVE) {
    @Override
    public int getFragmentId() {
      throw makeUnsupportedException("getFragmentId");
    }

    @Override
    public List getStatements() {
      throw makeUnsupportedException("getStatements");
    }

    @Override
    public void setStatements(List statements) {
      throw makeUnsupportedException("setStatements");
    }

    @Override
    public void addStatements(List statements) {
      throw makeUnsupportedException("addStatements");
    }

    @Override
    public Set getRunAsyncs() {
      throw makeUnsupportedException("getRunAsyncs");
    }

    @Override
    public void addRunAsync(JRunAsync runAsync) {
      throw makeUnsupportedException("addSplitPoint");
    }

    @Override
    public void setFragmentId(int fragmentId) {
      throw makeUnsupportedException("setFragmentId");
    }

    private UnsupportedOperationException makeUnsupportedException(String methodName) {
      return new UnsupportedOperationException(methodName + " is not supported in the "
          + "dummy NOT_EXCLUSIVE fragment");
    }
  };

  /**
   * Gets the liveness predicate for fragment.
   */
  LivenessPredicate getLivenessPredicate(Fragment fragment) {
    return new ExclusivityMapLivenessPredicate(fragment);
  }

  /**
   * Determine whether a field is live in a fragment.
   */
  public boolean isLiveInFragment(Fragment fragment, JField field) {
    return isLiveInFragment(fragmentForField, field, fragment);
  }

  /**
   * Determine whether a method is live in a fragment.
   */
  public boolean isLiveInFragment(Fragment fragment, JMethod method) {
    return isLiveInFragment(fragmentForMethod, method, fragment);
  }

  /**
   * Determine whether a type is live in a fragment.
   */
  public boolean isLiveInFragment(Fragment fragment, JDeclaredType type) {
    return isLiveInFragment(fragmentForType, type, fragment);
  }

  private Map fragmentForField = Maps.newHashMap();
  private Map fragmentForMethod = Maps.newHashMap();
  private Map fragmentForType = Maps.newHashMap();

  /**
   * Traverse {@code exp} and find all referenced class literals.
   */
  private static Set classLiteralsIn(JExpression expression) {
    final Set literals = Sets.newHashSet();
    new JVisitor() {
      @Override
      public void endVisit(JClassLiteral classLiteral, Context ctx) {
        literals.add(classLiteral);
      }
    }.accept(expression);
    return literals;
  }

  /**
   * Traverse {@code exp} and find all referenced JMethods.
   */
  private static Set methodsReferencesIn(JExpression expression) {
    final Set methods = Sets.newHashSet();
    new JVisitor() {
      @Override
      public void endVisit(JsniMethodRef jsniMethodRef, Context ctx) {
        methods.add(jsniMethodRef.getTarget());
      }
    }.accept(expression);
    return methods;
  }

  /**
   * Map atoms to exclusive fragments. Do this by trying to find code atoms that
   * are only needed by a single split point. Such code can be moved to the
   * exclusively live fragment associated with that split point.
   */
  public static ExclusivityMap computeExclusivityMap(Collection exclusiveFragments,
      ControlFlowAnalyzer completeCfa,
      Map notExclusiveCfaByFragment) {
    ExclusivityMap exclusivityMap = new ExclusivityMap();
    exclusivityMap.compute(exclusiveFragments, completeCfa, notExclusiveCfaByFragment);
    return exclusivityMap;
  }

  /**
   * 

* Patch up the fragment map to satisfy load-order dependencies, as described * in the comment of {@link LivenessPredicate}. * Load-order dependencies can be * violated when an atom is mapped to 0 as a leftover, but it has some * load-order dependency on an atom that was put in an exclusive fragment. *

* *

* In general, it might be possible to split things better by considering load * order dependencies when building the fragment map. However, fixing them * after the fact makes CodeSplitter simpler. In practice, for programs tried * so far, there are very few load order dependency fixups that actually * happen, so it seems better to keep the compiler simpler. *

* *

* It would be safer and more robust to include the load order dependencies * in the general scheme and uniformly use control flow analysis to determine * dependencies instead of hand picking atoms to check and fix. Also note that * some of the control flow and load dependencies are introduced as the Java * AST is translated into JavaScript and hence not visible by ControlFlowAnalyzer. *

* *

* Furthermore, in some cases actual dependencies differ between Java AST and the * final JavaScript output. For example whether a field initialization is done at declaration * or during instance creation decided by * {@link GenerateJavaScriptAST.GenerateJavaScriptVisitor#initializeAtTopScope}. Mismatches * like these are handled explicitly by these fixup passes. *

*/ public void fixUpLoadOrderDependencies(TreeLogger logger, JProgram jprogram, Set methodsStillInJavaScript) { fixUpLoadOrderDependenciesForMethods(logger, jprogram, methodsStillInJavaScript); fixUpLoadOrderDependenciesForTypes(logger, jprogram); fixUpLoadOrderDependenciesForClassLiterals(logger, jprogram, methodsStillInJavaScript); } /** * Map atoms to exclusive fragments. Do this by trying to find code atoms that * are only needed by a single split point. Such code can be moved to the * exclusively live fragment associated with that split point. */ private void compute(Collection exclusiveFragments, ControlFlowAnalyzer completeCfa, Map notExclusiveCfaByFragment) { Set allLiveFields = filter(Sets.union(completeCfa.getLiveFieldsAndMethods(), completeCfa.getFieldsWritten()), JField.class); Set allLiveMethods = filter(completeCfa.getLiveFieldsAndMethods(), JMethod.class); Set allLiveTypes = filter(completeCfa.getInstantiatedTypes(), JDeclaredType.class); for (Fragment fragment : exclusiveFragments) { assert fragment.isExclusive(); ControlFlowAnalyzer complementCfa = notExclusiveCfaByFragment.get(fragment); Set nodesNotExclusiveToFragment = Sets.union(complementCfa.getLiveFieldsAndMethods(), complementCfa.getFieldsWritten()); putIfAbsent(fragmentForField, fragment, Sets.difference(allLiveFields, nodesNotExclusiveToFragment)); putIfAbsent(fragmentForMethod, fragment, Sets.difference(allLiveMethods, complementCfa.getLiveFieldsAndMethods())); putIfAbsent(fragmentForType, fragment, Sets.difference(allLiveTypes, filter(complementCfa.getInstantiatedTypes(), JDeclaredType.class))); } // Assign all living atoms to left overs. putIfAbsent(fragmentForField, NOT_EXCLUSIVE, allLiveFields); putIfAbsent(fragmentForMethod, NOT_EXCLUSIVE, allLiveMethods); putIfAbsent(fragmentForType, NOT_EXCLUSIVE, allLiveTypes); } /** * A class literal cannot be loaded until all the parameters to its createFor... class are. * Make sure that the strings are available for all class literals at the time they are * loaded and make sure that superclass class literals are loaded before. */ private void fixUpLoadOrderDependenciesForClassLiterals( TreeLogger logger, JProgram jprogram, Set methodsStillInJavaScript) { int numFixups = 0; /** * Consider all static fields of ClassLiteralHolder; the majority if not all its static * fields are class literal fields. It is safe to fix up extra fields. */ Queue potentialClassLiteralFields = new ArrayDeque( jprogram.getTypeClassLiteralHolder().getFields()); int numClassLiterals = potentialClassLiteralFields.size(); while (!potentialClassLiteralFields.isEmpty()) { JField field = potentialClassLiteralFields.remove(); if (!field.isStatic()) { continue; } Fragment classLiteralFragment = fragmentForField.get(field); // In -XenableClosureFormat creation of class literals needs to happen before or with class // definition. This fixup takes care when it is not the case. JType type = jprogram.getTypeByClassLiteralField(field); Fragment classLiteralTypeFragment = fragmentForType.get(type); if (!canReferenceAtomsFrom(classLiteralTypeFragment, classLiteralFragment)) { numFixups++; fragmentForField.put(field, NOT_EXCLUSIVE); classLiteralFragment = NOT_EXCLUSIVE; } JExpression initializer = field.getInitializer(); // Fixup the superclass class literals. for (JClassLiteral superclassClassLiteral : classLiteralsIn(initializer)) { JField superclassClassLiteralField = superclassClassLiteral.getField(); // Fix the super class literal and add it to the reexamined. Fragment superclassClassLiteralFragment = fragmentForField.get(superclassClassLiteralField); if (!canReferenceAtomsFrom(classLiteralFragment, superclassClassLiteralFragment)) { numFixups++; fragmentForField.put(superclassClassLiteralField, NOT_EXCLUSIVE); // Add the field back so that its superclass class literal gets fixed if necessary. potentialClassLiteralFields.add(superclassClassLiteralField); } } // If there are references to methods move those as well. In particular the enum class // literals reference the static methods values() and valueOf() for the particular enum type // those methods need to be defined before the class literal. for (JMethod referencedMethod : methodsReferencesIn(initializer)) { // Move the referenced methods if necessary. Fragment referencedMethodFragment = fragmentForMethod.get(referencedMethod); if (methodsStillInJavaScript.contains(referencedMethod) && !canReferenceAtomsFrom(classLiteralFragment, referencedMethodFragment)) { assert referencedMethod.isStatic(); numFixups++; fragmentForMethod.put(referencedMethod, NOT_EXCLUSIVE); } } } logger.log(TreeLogger.DEBUG, "Fixed up load-order dependencies by moving " + numFixups + " fields in class literal constructors to fragment 0, out of " + numClassLiterals); } /** * Fixes up the load-order dependencies from instance methods to their enclosing types, in some * cases there is some freedom to place instance methods in one of two or more exclusive * fragment. That scenario arises when an instance method is only accessible after two or * more exclusive fragments have been loaded. In such scenario this fixup will move the method * to the fragment where the type is instantiated. */ private void fixUpLoadOrderDependenciesForMethods(TreeLogger logger, JProgram jprogram, Set methodsStillInJavaScript) { int numFixups = 0; for (JDeclaredType type : jprogram.getDeclaredTypes()) { Fragment typeFrag = fragmentForType.get(type); if (typeFrag == null || !typeFrag.isExclusive()) { continue; } /* * If the type is in an exclusive fragment, all its instance methods must be in the same one; * if this is not the case move the type to the NOT_EXCLUSIVE fragment. */ for (JMethod method : type.getMethods()) { if (method.needsDynamicDispatch() && methodsStillInJavaScript.contains(method) && typeFrag != fragmentForMethod.get(method)) { fragmentForType.put(type, NOT_EXCLUSIVE); numFixups++; break; } } } logger.log(TreeLogger.DEBUG, "Fixed up load-order dependencies for instance methods by moving " + numFixups + " types to fragment 0, out of " + jprogram.getDeclaredTypes().size()); } /** * Fixes up load order dependencies from types to their supertypes. */ private void fixUpLoadOrderDependenciesForTypes(TreeLogger logger, JProgram jprogram) { int numFixups = 0; Queue typesToCheck = new ArrayDeque(jprogram.getDeclaredTypes().size()); typesToCheck.addAll(jprogram.getDeclaredTypes()); while (!typesToCheck.isEmpty()) { JDeclaredType type = typesToCheck.remove(); if (type.getSuperClass() != null) { Fragment typeFrag = fragmentForType.get(type); Fragment supertypeFrag = fragmentForType.get(type.getSuperClass()); if (!canReferenceAtomsFrom(typeFrag, supertypeFrag)) { numFixups++; fragmentForType.put(type.getSuperClass(), NOT_EXCLUSIVE); typesToCheck.add(type.getSuperClass()); } } } logger.log(TreeLogger.DEBUG, "Fixed up load-order dependencies on supertypes by moving " + numFixups + " types to fragment 0, out of " + jprogram.getDeclaredTypes().size()); } private static Set filter(Set types, Class clazz) { return (Set) Sets.filter(types, Predicates.instanceOf(clazz)); } /** * Returns true if atoms in thatFragment are visible from thisFragment. */ private static boolean canReferenceAtomsFrom(Fragment thisFragment, Fragment thatFragment) { return thisFragment == null || thisFragment == thatFragment || !thatFragment.isExclusive(); } /** * An atom is live in a fragment if either it is exclusive to that fragment or not exclusive * to any fragment. */ private static boolean isLiveInFragment(Map map, T atom, Fragment expectedFragment) { Fragment actualFragment = map.get(atom); return actualFragment != null && (expectedFragment == actualFragment || !actualFragment.isExclusive()); } private void putIfAbsent(Map map, Fragment fragment, Iterable atoms) { for (T atom : atoms) { if (!map.containsKey(atom)) { // Some atoms might atoms might be dead until both split points i and j are reached, and // thus they could be assigned to either. // We choose here to assign to the first fragment, so that we could use this method // to assign leftovers. map.put(atom, fragment); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy