Skip to content

Commit

Permalink
Move definitions to smallest dependent subtree.
Browse files Browse the repository at this point in the history
Improve the selection of where to move code by always picking the
smallest module subtree that contains all references to the moved
definition. Before this change the preferred module was recalculated as
each reference was found, and this piecemeal approach can lead to
sub-optimal choices.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=157590940
  • Loading branch information
brad4d committed May 31, 2017
1 parent 7030303 commit 2a4317f
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 42 deletions.
95 changes: 61 additions & 34 deletions src/com/google/javascript/jscomp/CrossModuleCodeMotion.java
Expand Up @@ -19,12 +19,12 @@
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;


import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.CodingConvention.SubclassRelationship; import com.google.javascript.jscomp.CodingConvention.SubclassRelationship;
import com.google.javascript.rhino.IR; import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token; import com.google.javascript.rhino.Token;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.BitSet;
import java.util.Deque; import java.util.Deque;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
Expand Down Expand Up @@ -105,10 +105,11 @@ private void moveCode() {
if (info.shouldBeMoved()) { if (info.shouldBeMoved()) {
Iterator<Declaration> it = info.declarationIterator(); Iterator<Declaration> it = info.declarationIterator();
// Find the appropriate spot to move it to // Find the appropriate spot to move it to
Node destParent = moduleVarParentMap.get(info.preferredModule); JSModule preferredModule = info.getPreferredModule();
Node destParent = moduleVarParentMap.get(preferredModule);
if (destParent == null) { if (destParent == null) {
destParent = compiler.getNodeForCodeInsertion(info.preferredModule); destParent = compiler.getNodeForCodeInsertion(preferredModule);
moduleVarParentMap.put(info.preferredModule, destParent); moduleVarParentMap.put(preferredModule, destParent);
} }
while (it.hasNext()) { while (it.hasNext()) {
Declaration decl = it.next(); Declaration decl = it.next();
Expand All @@ -131,7 +132,7 @@ private void moveCode() {
} }
// Update variable declaration location. // Update variable declaration location.
info.wasMoved = true; info.wasMoved = true;
info.declModule = info.preferredModule; info.declModule = preferredModule;
} }
} }
} }
Expand All @@ -140,9 +141,11 @@ private void moveCode() {
private class NamedInfo { private class NamedInfo {
boolean allowMove = true; boolean allowMove = true;


// All modules containing references to this global variable directly or transitively depend // If movement is allowed, this will be filled with the indices of all modules referring to
// on this module, which is potentially further down the tree than the module containing the // the global name.
// variable's declaration statements. private BitSet modulesWithReferences = null;

// A place to stash the results of getPreferredModule() to avoid recalculating it unnecessarily.
private JSModule preferredModule = null; private JSModule preferredModule = null;


// The module where declarations appear // The module where declarations appear
Expand All @@ -154,21 +157,51 @@ private class NamedInfo {
private final Deque<Declaration> declarations = private final Deque<Declaration> declarations =
new ArrayDeque<>(); new ArrayDeque<>();


boolean isAllowedToMove() {
return allowMove;
}

void disallowMovement() {
allowMove = false;
// If we cannot move it, there's no point tracking where it's used.
modulesWithReferences = null;
preferredModule = declModule;
}

// Add a Module where it is used // Add a Module where it is used
void addUsedModule(JSModule m) { void addUsedModule(JSModule m) {
// If we are not allowed to move it, all bets are off // If we are not allowed to move it, don't waste time and space tracking modules with
if (!allowMove) { // references.
return; if (allowMove) {
if (modulesWithReferences == null) {
// first call to this method
modulesWithReferences = new BitSet(graph.getModuleCount());
}
modulesWithReferences.set(m.getIndex());
// invalidate preferredModule, so it will be recalculated next time getPreferredModule() is
// called.
preferredModule = null;
} }
}


// If we have no preferred module yet, set this one /**
* Returns the root module of a dependency subtree that contains all of the modules which refer
* to this global name.
*/
JSModule getPreferredModule() {
// It doesn't even make sense to call this method if the declarations cannot be moved.
checkState(allowMove);
if (preferredModule == null) { if (preferredModule == null) {
preferredModule = m; if (modulesWithReferences == null) {
} else { // If we saw no references, we must at least have seen a declaration.
// Find the deepest common dependency preferredModule = checkNotNull(declModule);
preferredModule = } else {
graph.getSmallestCoveringDependency(ImmutableList.of(m, preferredModule)); // Note that getSmallestCoveringDependency() will do this:
// checkState(!modulesWithReferences.isEmpty())
preferredModule = graph.getSmallestCoveringDependency(modulesWithReferences);
}
} }
return preferredModule;
} }


/** /**
Expand All @@ -190,13 +223,8 @@ boolean shouldBeMoved() {
// Only move if all are true: // Only move if all are true:
// a) allowMove is true // a) allowMove is true
// b) it is declared somewhere (declModule != null) // b) it is declared somewhere (declModule != null)
// c) it was used somewhere (preferredModule != null) // c) the all usages depend on the declModule by way of a different, preferred module
// [if not, then it will be removed as dead or invalid code elsewhere] return allowMove && declModule != null && graph.dependsOn(getPreferredModule(), declModule);
// d) the all usages depend on the declModule by way of preferredModule
return allowMove
&& preferredModule != null
&& declModule != null
&& graph.dependsOn(preferredModule, declModule);
} }


/** /**
Expand Down Expand Up @@ -310,12 +338,11 @@ private void collectReferences(Node root) {


for (Var v : collector.getAllSymbols()) { for (Var v : collector.getAllSymbols()) {
NamedInfo info = getNamedInfo(v); NamedInfo info = getNamedInfo(v);
if (!info.allowMove) { if (info.isAllowedToMove()) {
continue; ReferenceCollection refCollection = collector.getReferences(v);
} for (Reference ref : refCollection) {
ReferenceCollection refCollection = collector.getReferences(v); processReference(collector, ref, info, v);
for (Reference ref : refCollection) { }
processReference(collector, ref, info, v);
} }
} }
} }
Expand All @@ -335,7 +362,7 @@ private void processReference(
// we would need to skip the parent in this check as the name could // we would need to skip the parent in this check as the name could
// just be a function itself. // just be a function itself.
if (hasConditionalAncestor(parent.getParent())) { if (hasConditionalAncestor(parent.getParent())) {
info.allowMove = false; info.disallowMovement();
} }
} else { } else {
if (!parentModuleCanSeeSymbolsDeclaredInChildren) { if (!parentModuleCanSeeSymbolsDeclaredInChildren) {
Expand Down Expand Up @@ -584,9 +611,9 @@ private static boolean canMoveValue(
} }


/** /**
* Transforms instanceof usages into an expression that short circuits to * Transforms instanceof usages into an expression that short circuits to false if tested with a
* false if tested with a constructor that is undefined. This allows ignoring * constructor that is undefined. This allows ignoring instanceof with respect to cross module
* instanceof with respect to cross module code motion. * code motion.
*/ */
private void addInstanceofGuards() { private void addInstanceofGuards() {
Node tmp = IR.block(); Node tmp = IR.block();
Expand Down
9 changes: 5 additions & 4 deletions src/com/google/javascript/jscomp/JSModuleGraph.java
Expand Up @@ -268,10 +268,10 @@ public boolean dependsOn(JSModule src, JSModule m) {
* <p>If multiple candidates have the same number of dependents, the module farthest down in the * <p>If multiple candidates have the same number of dependents, the module farthest down in the
* total ordering of modules will be chosen. * total ordering of modules will be chosen.
* *
* @param dependentModules to consider * @param dependentModules indices of modules to consider
* @return A module on which all of the argument modules depend * @return A module on which all of the argument modules depend
*/ */
public JSModule getSmallestCoveringDependency(Collection<JSModule> dependentModules) { public JSModule getSmallestCoveringDependency(BitSet dependentModules) {
checkState(!dependentModules.isEmpty()); checkState(!dependentModules.isEmpty());


// Candidate modules are those that all of the given dependent modules depend on, including // Candidate modules are those that all of the given dependent modules depend on, including
Expand All @@ -280,8 +280,9 @@ public JSModule getSmallestCoveringDependency(Collection<JSModule> dependentModu
int minDependentModuleIndex = modules.length; int minDependentModuleIndex = modules.length;
final BitSet candidates = new BitSet(modules.length); final BitSet candidates = new BitSet(modules.length);
candidates.set(0, modules.length, true); candidates.set(0, modules.length, true);
for (JSModule module : dependentModules) { for (int dependentIndex = dependentModules.nextSetBit(0);
int dependentIndex = module.getIndex(); dependentIndex >= 0;
dependentIndex = dependentModules.nextSetBit(dependentIndex + 1)) {
minDependentModuleIndex = Math.min(minDependentModuleIndex, dependentIndex); minDependentModuleIndex = Math.min(minDependentModuleIndex, dependentIndex);
candidates.and(selfPlusTransitiveDeps[dependentIndex]); candidates.and(selfPlusTransitiveDeps[dependentIndex]);
} }
Expand Down
12 changes: 8 additions & 4 deletions test/com/google/javascript/jscomp/JSModuleGraphTest.java
Expand Up @@ -26,7 +26,7 @@
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.BitSet;
import java.util.List; import java.util.List;
import junit.framework.TestCase; import junit.framework.TestCase;


Expand Down Expand Up @@ -394,16 +394,20 @@ private List<String> requires(String ... strings) {
} }


private void assertSmallestCoveringDependency(JSModule expected, JSModule... modules) { private void assertSmallestCoveringDependency(JSModule expected, JSModule... modules) {
assertSmallestCoveringDependency(expected, graph, Arrays.asList(modules)); assertSmallestCoveringDependency(expected, graph, modules);
} }


private void assertSmallestCoveringDependency( private void assertSmallestCoveringDependency(
JSModule expected, JSModuleGraph graph, JSModule... modules) { JSModule expected, JSModuleGraph graph, JSModule... modules) {
assertSmallestCoveringDependency(expected, graph, Arrays.asList(modules)); BitSet modulesBitSet = new BitSet();
for (JSModule m : modules) {
modulesBitSet.set(m.getIndex());
}
assertSmallestCoveringDependency(expected, graph, modulesBitSet);
} }


private void assertSmallestCoveringDependency( private void assertSmallestCoveringDependency(
JSModule expected, JSModuleGraph graph, Collection<JSModule> modules) { JSModule expected, JSModuleGraph graph, BitSet modules) {
JSModule actual = graph.getSmallestCoveringDependency(modules); JSModule actual = graph.getSmallestCoveringDependency(modules);
assertWithMessage( assertWithMessage(
"Smallest covering dep of %s should be %s but was %s", modules, expected, actual) "Smallest covering dep of %s should be %s but was %s", modules, expected, actual)
Expand Down

0 comments on commit 2a4317f

Please sign in to comment.