Skip to content

Commit

Permalink
Simplify generators with a few (< 4) "case"s and inline them directly…
Browse files Browse the repository at this point in the history
… so that:

  switch ($context.nextAddress) {
    case 1: a();
    case 2: b();
    case 3: c();
  }
is simplified to:
  if ($context.nextAddress == 1) a();
  if ($context.nextAddress != 3) b();
  c();

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=192259507
  • Loading branch information
SergejSalnikov authored and tjgq committed Apr 10, 2018
1 parent 61c851d commit 49e24ed
Show file tree
Hide file tree
Showing 3 changed files with 462 additions and 377 deletions.
129 changes: 106 additions & 23 deletions src/com/google/javascript/jscomp/Es6RewriteGenerators.java
Expand Up @@ -292,26 +292,16 @@ public void transpile() {
genFuncName.setString(context.getScopedName(GENERATOR_FUNCTION).getString()); genFuncName.setString(context.getScopedName(GENERATOR_FUNCTION).getString());
} }


// switch ($jscomp$generator$context.nextAddress) { Node generatorBody = IR.block();
// }
Node switchNode = IR.switchNode(context.getContextField(genFunc, "nextAddress"));


// Prepare a "program" function: // Prepare a "program" function:
// function ($jscomp$generator$context) { // function ($jscomp$generator$context) {
// do switch ($jscomp$generator$context.nextAddress) {
// } while (0);
// } // }
Node program = Node program =
IR.function( IR.function(
IR.name(""), IR.name(""),
IR.paramList(context.getJsContextNameNode(genFunc)), IR.paramList(context.getJsContextNameNode(originalGeneratorBody)),
IR.block( generatorBody);
// Without the while loop, OTI assumes the switch statement is only executed
// once, which messes up its understanding of the types assigned to variables
// within it.
IR.doNode( // TODO(skill): Remove do-while loop when this pass moves after
// type checking or when OTI is fixed to handle this correctly.
IR.block(switchNode), withType(IR.number(0), numberType))));


// Propagate all suppressions from original generator function to a new "program" function. // Propagate all suppressions from original generator function to a new "program" function.
JSDocInfoBuilder jsDocBuilder = new JSDocInfoBuilder(false); JSDocInfoBuilder jsDocBuilder = new JSDocInfoBuilder(false);
Expand Down Expand Up @@ -355,8 +345,7 @@ public void transpile() {


// Transpile statements from original generator function // Transpile statements from original generator function
while (originalGeneratorBody.hasChildren()) { while (originalGeneratorBody.hasChildren()) {
Node statement = originalGeneratorBody.getFirstChild().detach(); transpileStatement(originalGeneratorBody.removeFirstChild());
transpileStatement(statement);
} }


// Ensure that the state machine program ends // Ensure that the state machine program ends
Expand All @@ -368,7 +357,7 @@ public void transpile() {
context.currentCase.jumpTo(context.programEndCase, finalBlock); context.currentCase.jumpTo(context.programEndCase, finalBlock);
context.currentCase.mayFallThrough = true; context.currentCase.mayFallThrough = true;


context.finalizeTransformation(switchNode); context.finalizeTransformation(generatorBody);
context.checkStateIsEmpty(); context.checkStateIsEmpty();


genFunc.putBooleanProp(Node.GENERATOR_FN, false); genFunc.putBooleanProp(Node.GENERATOR_FN, false);
Expand Down Expand Up @@ -1112,9 +1101,12 @@ private class TranspilationContext {
final ArrayDeque<Case> finallyCases = new ArrayDeque<>(); final ArrayDeque<Case> finallyCases = new ArrayDeque<>();
final HashSet<String> catchNames = new HashSet<>(); final HashSet<String> catchNames = new HashSet<>();


/** All case sections that will be added to generator program. */ /** All "case" sections that will be added to generator program. */
final ArrayList<Case> allCases = new ArrayList<>(); final ArrayList<Case> allCases = new ArrayList<>();


/** All "break" nodes that exit from the generator primary switch statement */
final ArrayList<Node> switchBreaks = new ArrayList<>();

/** A virtual case that indicates end of program */ /** A virtual case that indicates end of program */
final Case programEndCase; final Case programEndCase;


Expand Down Expand Up @@ -1273,10 +1265,78 @@ void optimizeCaseIds() {
} }
} }


/**
* Replaces "...; break;" with "return ...;".
*/
void eliminateSwitchBreaks() {
for (Node breakNode : switchBreaks) {
Node prevStatement = breakNode.getPrevious();
checkState(prevStatement != null);
checkState(prevStatement.isExprResult());
prevStatement.replaceWith(IR.returnNode(prevStatement.removeFirstChild()));
breakNode.detach();
}
switchBreaks.clear();
}

/** Finalizes transpilation by dumping all generated "case" nodes. */ /** Finalizes transpilation by dumping all generated "case" nodes. */
public void finalizeTransformation(Node switchNode) { public void finalizeTransformation(Node generatorBody) {
optimizeCaseIds(); optimizeCaseIds();
// Write all state machine cases
// If number of cases is small we render them without using "switch"
// switch ($context.nextAddress) {
// case 1: a();
// case 2: b();
// case 3: c();
// }
// are rendered as:
// if ($context.nextAddress == 1) a();
// if ($context.nextAddress != 3) b();
// c();
if (allCases.size() == 2 || allCases.size() == 3) {
generatorBody.addChildToBack(
IR.ifNode(
withType(
IR.eq(getContextField(generatorBody, "nextAddress"),
withType(IR.number(1), numberType)), booleanType),
allCases.remove(0).caseBlock).useSourceInfoIfMissingFromForTree(generatorBody));
}

// If number of cases is small we render them without using "switch"
// switch ($context.nextAddress) {
// case 1: a();
// case 2: b();
// }
// are rendered as:
// if ($context.nextAddress == 1) a();
// b();
if (allCases.size() == 2) {
generatorBody.addChildToBack(
IR.ifNode(
withType(
IR.ne(getContextField(generatorBody, "nextAddress"),
withType(IR.number(allCases.get(1).id), numberType)), booleanType),
allCases.remove(0).caseBlock).useSourceInfoIfMissingFromForTree(generatorBody));
}

// If number of cases is small we render them without using "switch"
// switch ($context.nextAddress) {
// case 1: a();
// }
// are rendered as:
// a();
if (allCases.size() == 1) {
generatorBody.addChildrenToBack(allCases.remove(0).caseBlock.removeChildren());
eliminateSwitchBreaks();
return;
}

// switch ($jscomp$generator$context.nextAddress) {}
Node switchNode = IR.switchNode(getContextField(generatorBody, "nextAddress"))
.useSourceInfoFrom(generatorBody);
generatorBody.addChildToBack(switchNode);

// Populate "switch" statement with "case"s.
for (Case currentCase : allCases) { for (Case currentCase : allCases) {
switchNode.addChildToBack(currentCase.createCaseNode()); switchNode.addChildToBack(currentCase.createCaseNode());
} }
Expand Down Expand Up @@ -1312,7 +1372,7 @@ void writeGeneratedNode(Node n) {
/** Adds a new generated node to the end of the current case and finializes it. */ /** Adds a new generated node to the end of the current case and finializes it. */
void writeGeneratedNodeAndBreak(Node n) { void writeGeneratedNodeAndBreak(Node n) {
writeGeneratedNode(n); writeGeneratedNode(n);
writeGeneratedNode(IR.breakNode().useSourceInfoFrom(n)); writeGeneratedNode(createBreakNodeFor(n));
currentCase.mayFallThrough = false; currentCase.mayFallThrough = false;
} }


Expand Down Expand Up @@ -1375,6 +1435,29 @@ Node returnContextMethod(Node sourceNode, String methodName, Node... args) {
.useSourceInfoFrom(sourceNode); .useSourceInfoFrom(sourceNode);
} }


/**
* Creates a "break;" statement that will follow {@code preBreak} node.
*
* <p>This is used to be able to generatate a state machine program outside of "swtich"
* statement so:
*
* <pre>
* $context.jumpTo(5);
* break;
* </pre>
*
* could be converted into:
*
* <pre>
* return $context.jumpTo(5);
* </pre>
*/
Node createBreakNodeFor(Node preBreak) {
Node breakNode = IR.breakNode().useSourceInfoFrom(preBreak);
switchBreaks.add(breakNode);
return breakNode;
}

/** /**
* Returns a node that instructs a state machine program to jump to a selected case section. * Returns a node that instructs a state machine program to jump to a selected case section.
*/ */
Expand All @@ -1398,7 +1481,7 @@ Node createJumpToBlock(Case section, boolean allowEmbedding, Node sourceNode) {
checkState(section.embedInto == null); checkState(section.embedInto == null);
Node jumpBlock = IR.block( Node jumpBlock = IR.block(
callContextMethodResult(sourceNode, "jumpTo", section.getNumber(sourceNode)), callContextMethodResult(sourceNode, "jumpTo", section.getNumber(sourceNode)),
IR.breakNode().useSourceInfoFrom(sourceNode)) createBreakNodeFor(sourceNode))
.useSourceInfoFrom(sourceNode); .useSourceInfoFrom(sourceNode);
if (allowEmbedding) { if (allowEmbedding) {
section.embedInto = jumpBlock; section.embedInto = jumpBlock;
Expand All @@ -1422,7 +1505,7 @@ void replaceBreakContinueWithJump(Node sourceNode, Case section, int breakSuppre
sourceNode.getParent().addChildBefore( sourceNode.getParent().addChildBefore(
callContextMethodResult(sourceNode, jumpMethod, section.getNumber(sourceNode)), callContextMethodResult(sourceNode, jumpMethod, section.getNumber(sourceNode)),
sourceNode); sourceNode);
sourceNode.replaceWith(IR.breakNode()); sourceNode.replaceWith(createBreakNodeFor(sourceNode));
} else { } else {
// "break;" inside a loop or swtich statement: // "break;" inside a loop or swtich statement:
// for (...) { // for (...) {
Expand Down Expand Up @@ -1762,7 +1845,7 @@ Node getNumber(Node sourceNode) {
if (jumpTo != null) { if (jumpTo != null) {
return jumpTo.getNumber(sourceNode); return jumpTo.getNumber(sourceNode);
} }
Node node = withType(IR.number(id).useSourceInfoFrom(sourceNode), numberType); Node node = withType(IR.number(id), numberType).useSourceInfoFrom(sourceNode);
references.add(node); references.add(node);
return node; return node;
} }
Expand Down

0 comments on commit 49e24ed

Please sign in to comment.