JS: inline all calls in a statement in one pass

This commit is contained in:
Alexey Tsvetkov
2015-03-27 21:10:52 +03:00
parent c8453ec0d9
commit 05c44db80f
6 changed files with 101 additions and 89 deletions
@@ -5,6 +5,9 @@
package com.google.dart.compiler.backend.js.ast; package com.google.dart.compiler.backend.js.ast;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import java.util.List;
/** /**
* The context in which a JsNode visitation occurs. This represents the set of * The context in which a JsNode visitation occurs. This represents the set of
@@ -13,9 +16,19 @@ import org.jetbrains.annotations.Nullable;
*/ */
public abstract class JsContext<T extends JsNode> { public abstract class JsContext<T extends JsNode> {
public abstract <R extends T> void insertAfter(R node); public <R extends T> void addPrevious(R node) {
throw new NotImplementedException();
}
public abstract <R extends T> void insertBefore(R node); public <R extends T> void addPrevious(List<R> nodes) {
for (R node : nodes) {
addPrevious(node);
}
}
public <R extends T> void addNext(R node) {
throw new NotImplementedException();
}
public abstract void removeMe(); public abstract void removeMe();
@@ -26,8 +26,7 @@ import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.*;
import java.util.Stack;
/** /**
* A visitor for iterating through and modifying an AST. * A visitor for iterating through and modifying an AST.
@@ -37,47 +36,71 @@ public class JsVisitorWithContextImpl extends JsVisitorWithContext {
private final Stack<JsContext<JsStatement>> statementContexts = new Stack<JsContext<JsStatement>>(); private final Stack<JsContext<JsStatement>> statementContexts = new Stack<JsContext<JsStatement>>();
public class ListContext<T extends JsNode> extends JsContext<T> { public class ListContext<T extends JsNode> extends JsContext<T> {
private List<T> collection; private List<T> nodes;
private int index; private int index;
// Those are reset in every iteration of traverse()
private final List<T> previous = new SmartList<T>();
private final List<T> next = new SmartList<T>();
private boolean removed = false;
@Override @Override
public <R extends T> void insertAfter(R node) { public <R extends T> void addPrevious(R node) {
//noinspection unchecked previous.add(node);
collection.add(index + 1, (T) node);
} }
@Override @Override
public <R extends T> void insertBefore(R node) { public <R extends T> void addNext(R node) {
//noinspection unchecked next.add(node);
collection.add(index++, (T) node);
} }
@Override @Override
public void removeMe() { public void removeMe() {
collection.remove(index--); removed = true;
} }
@Override @Override
public <R extends T> void replaceMe(R node) { public <R extends T> void replaceMe(R node) {
checkReplacement(collection.get(index), node); checkReplacement(nodes.get(index), node);
collection.set(index, node); nodes.set(index, node);
removed = false;
} }
@Nullable @Nullable
@Override @Override
public T getCurrentNode() { public T getCurrentNode() {
if (index < collection.size()) { if (!removed && index < nodes.size()) {
return collection.get(index); return nodes.get(index);
} }
return null; return null;
} }
protected void traverse(List<T> collection) { protected void traverse(List<T> nodes) {
this.collection = collection; assert previous.isEmpty(): "addPrevious() was called before traverse()";
for (index = 0; index < collection.size(); ++index) { assert next.isEmpty(): "addNext() was called before traverse()";
T node = collection.get(index); this.nodes = nodes;
doTraverse(node, this);
for (index = 0; index < nodes.size(); index++) {
removed = false;
previous.clear();
next.clear();
doTraverse(getCurrentNode(), this);
if (!previous.isEmpty()) {
nodes.addAll(index, previous);
index += previous.size();
}
if (removed) {
nodes.remove(index);
index--;
}
if (!next.isEmpty()) {
nodes.addAll(index + 1, next);
index += next.size();
}
} }
} }
} }
@@ -88,16 +111,6 @@ public class JsVisitorWithContextImpl extends JsVisitorWithContext {
private class NodeContext<T extends JsNode> extends JsContext<T> { private class NodeContext<T extends JsNode> extends JsContext<T> {
protected T node; protected T node;
@Override
public <R extends T> void insertAfter(R node) {
throw new UnsupportedOperationException();
}
@Override
public <R extends T> void insertBefore(R node) {
throw new UnsupportedOperationException();
}
@Override @Override
public void removeMe() { public void removeMe() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
@@ -48,36 +48,6 @@ public class JsInliner extends JsVisitorWithContextImpl {
private final Stack<JsFunction> namedFunctionsStack = new Stack<JsFunction>(); private final Stack<JsFunction> namedFunctionsStack = new Stack<JsFunction>();
private final LinkedList<JsCallInfo> inlineCallInfos = new LinkedList<JsCallInfo>(); private final LinkedList<JsCallInfo> inlineCallInfos = new LinkedList<JsCallInfo>();
/**
* A statement can contain more, than one inlineable sub-expressions.
* When inline call is expanded, current statement is shifted forward,
* but still has same statement context with same index on stack.
*
* The shifting is intentional, because there could be function literals,
* that need to be inlined, after expansion.
*
* After shifting following inline expansion in the same statement could be
* incorrect, because wrong statement index is used.
*
* To prevent this, after every shift this flag is set to true,
* so that visitor wont go deeper until statement is visited.
*
* Example:
* inline fun f(g: () -> Int): Int { val a = g(); return a }
* inline fun Int.abs(): Int = if (this < 0) -this else this
*
* val g = { 10 }
* >> val h = f(g).abs() // last statement context index
*
* val g = { 10 } // after inline
* >> val f$result // statement index was not changed
* val a = g()
* f$result = a
* val h = f$result.abs() // current expression still here; incorrect to inline abs(),
* // because statement context on stack point to different statement
*/
private boolean lastStatementWasShifted = false;
public static JsProgram process(@NotNull TranslationContext context) { public static JsProgram process(@NotNull TranslationContext context) {
JsProgram program = context.program(); JsProgram program = context.program();
IdentityHashMap<JsName, JsFunction> functions = collectNamedFunctions(program); IdentityHashMap<JsName, JsFunction> functions = collectNamedFunctions(program);
@@ -150,7 +120,7 @@ public class JsInliner extends JsVisitorWithContextImpl {
inline(call, context); inline(call, context);
} }
return !lastStatementWasShifted; return true;
} }
@Override @Override
@@ -175,36 +145,21 @@ public class JsInliner extends JsVisitorWithContextImpl {
JsStatement inlineableBody = inlineableResult.getInlineableBody(); JsStatement inlineableBody = inlineableResult.getInlineableBody();
JsExpression resultExpression = inlineableResult.getResultExpression(); JsExpression resultExpression = inlineableResult.getResultExpression();
JsContext<JsStatement> statementContext = inliningContext.getStatementContext(); JsContext<JsStatement> statementContext = inliningContext.getStatementContext();
// body of inline function can contain call to lambdas that need to be inlined
accept(inlineableBody); accept(inlineableBody);
statementContext.addPrevious(flattenStatement(inlineableBody));
/** /**
* Assumes, that resultExpression == null, when result is not needed. * Assumes, that resultExpression == null, when result is not needed.
* @see FunctionInlineMutator.isResultNeeded() * @see FunctionInlineMutator.isResultNeeded()
*/ */
if (resultExpression == null) { if (resultExpression == null) {
statementContext.removeCurrentStatement(); statementContext.removeMe();
} else { return;
context.replaceMe(resultExpression);
} }
/** @see #lastStatementWasShifted */ resultExpression = accept(resultExpression);
statementContext.shiftCurrentStatementForward(); context.replaceMe(resultExpression);
}
/**
* Prevents JsInliner from traversing sub-expressions,
* when current statement was shifted forward.
*/
@Override
protected <T extends JsNode> void doTraverse(T node, JsContext ctx) {
if (node instanceof JsStatement) {
/** @see #lastStatementWasShifted */
lastStatementWasShifted = false;
}
if (!lastStatementWasShifted) {
super.doTraverse(node, ctx);
}
} }
@NotNull @NotNull
@@ -36,7 +36,7 @@ class NamingContext(
if (renamingApplied) throw RuntimeException("RenamingContext has been applied already") if (renamingApplied) throw RuntimeException("RenamingContext has been applied already")
val result = replaceNames(target, renamings) val result = replaceNames(target, renamings)
statementContext.insertAllBefore(declarations) statementContext.addPrevious(declarations)
renamingApplied = true renamingApplied = true
return result return result
@@ -43,16 +43,17 @@ class ReturnReplacingVisitor(private val resultRef: JsNameRef?, private val brea
override fun endVisit(x: JsReturn?, ctx: JsContext<*>?) { override fun endVisit(x: JsReturn?, ctx: JsContext<*>?) {
if (x == null || ctx == null) return if (x == null || ctx == null) return
if (breakLabel != null) { ctx.removeMe()
ctx.insertAfter(JsBreak(breakLabel))
}
val returnReplacement = getReturnReplacement(x.getExpression()) val returnReplacement = getReturnReplacement(x.getExpression())
if (returnReplacement != null) { if (returnReplacement != null) {
ctx.insertBefore(JsExpressionStatement(returnReplacement)) ctx.addNext(JsExpressionStatement(returnReplacement))
}
if (breakLabel != null) {
ctx.addNext(JsBreak(breakLabel))
} }
ctx.removeMe()
} }
private fun getReturnReplacement(returnExpression: JsExpression?): JsExpression? { private fun getReturnReplacement(returnExpression: JsExpression?): JsExpression? {
@@ -0,0 +1,30 @@
package foo
// CHECK_CONTAINS_NO_CALLS: test
// A copy of stdlib run function.
// Copied to not to depend on run implementation.
// It's important, that the body is just `return fn()`.
inline fun evaluate<T>(fn: ()->T): T = fn()
fun test(n: Int): Int {
return evaluate {
var i = n
var sum = 0
while (i > 0) {
sum += i
i--
}
sum
}
}
fun box(): String {
assertEquals(6, test(3))
assertEquals(0, test(0))
assertEquals(0, test(-1))
return "OK"
}