JS inline: create JsInliningContext per function processed by JsInliner

This commit is contained in:
Alexey Tsvetkov
2014-08-11 20:20:08 +04:00
committed by Zalim Bashorov
parent 0151b7a2d1
commit 49718f143b
2 changed files with 101 additions and 70 deletions
@@ -17,36 +17,32 @@
package org.jetbrains.k2js.inline; package org.jetbrains.k2js.inline;
import com.google.dart.compiler.backend.js.ast.*; import com.google.dart.compiler.backend.js.ast.*;
import org.jetbrains.annotations.NotNull;
import java.util.*; import java.util.*;
class FunctionInlineMutator { class FunctionInlineMutator {
private static final String RESULT_LABEL = "result_inlined"; private JsBlock body;
private final JsInvocation call;
private final JsScope callerScope;
private final JsFunction invokedFunction; private final JsFunction invokedFunction;
private final List<JsStatement> statements = new ArrayList<JsStatement>();
private final IdentityHashMap<JsName, JsNameRef> renameMap = new IdentityHashMap<JsName, JsNameRef>();
public static InlineableResult getInlineableCallReplacement( public static InlineableResult getInlineableCallReplacement(
JsInvocation call, @NotNull JsInvocation call,
JsScope callerScope, @NotNull InliningContext inliningContext
JsFunction invokedFunction
) { ) {
return (new FunctionInlineMutator(call, callerScope, invokedFunction)).process(); FunctionInlineMutator mutator = new FunctionInlineMutator(call, inliningContext);
mutator.process();
JsStatement inlineableBody = mutator.body;
return new InlineableResult(inlineableBody, call);
} }
private FunctionInlineMutator(JsInvocation call, JsScope callerScope, JsFunction invokedFunction) { private FunctionInlineMutator(@NotNull JsInvocation call, @NotNull InliningContext inliningContext) {
this.call = call; FunctionContext functionContext = inliningContext.getFunctionContext();
this.callerScope = callerScope; invokedFunction = functionContext.getFunctionDefinition(call);
this.invokedFunction = invokedFunction; body = invokedFunction.getBody().deepCopy();
} }
private InlineableResult process() { private void process() {
JsBlock inlinedBody = new JsBlock(statements);
JsNameRef result = callerScope.declareName(RESULT_LABEL).makeRef();
statements.addAll(invokedFunction.getBody().getStatements());
return new InlineableResult(inlinedBody, result);
} }
} }
@@ -27,31 +27,79 @@ import static org.jetbrains.k2js.inline.FunctionInlineMutator.getInlineableCallR
public class JsInliner extends JsVisitorWithContextImpl { public class JsInliner extends JsVisitorWithContextImpl {
private class JsInliningContext implements InliningContext {
private final FunctionContext functionContext;
JsInliningContext(JsFunction function) {
functionContext = new FunctionContext(function, this) {
@Nullable
@Override
protected JsFunction lookUpStaticFunction(@Nullable JsName functionName) {
return functions.get(functionName);
}
};
}
@NotNull
@Override
public <T extends JsNode> RenamingContext<T> getRenamingContext() {
return new RenamingContext<T>(getFunctionContext().getScope());
}
@NotNull
@Override
public StatementContext getStatementContext() {
return new StatementContext() {
@NotNull
@Override
public JsContext getCurrentStatementContext() {
return getLastStatementLevelContext();
}
@NotNull
@Override
protected JsStatement getEmptyStatement() {
return getFunctionContext().getEmpty();
}
};
}
@NotNull
@Override
public FunctionContext getFunctionContext() {
return functionContext;
}
@Override
public boolean isResultNeeded(JsInvocation call) {
JsStatement currentStatement = getStatementContext().getCurrentStatement();
return InvocationUtil.isResultUsed(currentStatement, call);
}
}
private final IdentityHashMap<JsName, JsFunction> functions; private final IdentityHashMap<JsName, JsFunction> functions;
private final Stack<JsScope> scopeStack = new Stack<JsScope>(); private final Stack<JsInliningContext> inliningContexts = new Stack<JsInliningContext>();
private final JsProgram program;
public static JsProgram process(JsProgram program) { public static JsProgram process(JsProgram program) {
IdentityHashMap<JsName, JsFunction> functions = FunctionCollector.collectFunctions(program); IdentityHashMap<JsName, JsFunction> functions = FunctionCollector.collectFunctions(program);
JsInliner inliner = new JsInliner(program, functions); JsInliner inliner = new JsInliner(functions);
return inliner.process(); return inliner.accept(program);
} }
JsInliner(JsProgram program, IdentityHashMap<JsName, JsFunction> functions) { JsInliner(IdentityHashMap<JsName, JsFunction> functions) {
this.program = program;
this.functions = functions; this.functions = functions;
} }
@Override @Override
public boolean visit(JsFunction function, JsContext context) { public boolean visit(JsFunction function, JsContext context) {
scopeStack.push(function.getScope()); inliningContexts.push(new JsInliningContext(function));
return super.visit(function, context); return super.visit(function, context);
} }
@Override @Override
public void endVisit(JsFunction function, JsContext context) { public void endVisit(JsFunction function, JsContext context) {
super.endVisit(function, context); super.endVisit(function, context);
scopeStack.pop(); inliningContexts.pop();
} }
@Override @Override
@@ -66,65 +114,52 @@ public class JsInliner extends JsVisitorWithContextImpl {
inline(call, context); inline(call, context);
} }
} }
private JsProgram process() {
return this.accept(program);
}
private JsScope getCurrentFunctionScope() {
assert !scopeStack.isEmpty();
return scopeStack.peek();
}
private void inline(@NotNull JsInvocation call, @NotNull JsContext context) { private void inline(@NotNull JsInvocation call, @NotNull JsContext context) {
JsFunction functionToInline = findDeclaration(call); JsInliningContext inliningContext = getInliningContext();
assert functionToInline != null; FunctionContext functionContext = getFunctionContext();
JsContext statementLevelContext = getLastStatementLevelContext(); functionContext.declareFunctionConstructorCalls(call.getArguments());
assert statementLevelContext != null; InlineableResult inlineableResult = getInlineableCallReplacement(call, inliningContext);
JsScope currentScope = getCurrentFunctionScope();
InlineableResult inlineableResult = getInlineableCallReplacement(call, currentScope, functionToInline);
JsStatement inlineableBody = inlineableResult.getInlineableBody(); JsStatement inlineableBody = inlineableResult.getInlineableBody();
JsExpression resultExpression = inlineableResult.getResultExpression(); JsExpression resultExpression = inlineableResult.getResultExpression();
StatementContext statementContext = inliningContext.getStatementContext();
statementLevelContext.insertAfter(statementLevelContext.getCurrentNode()); /**
statementLevelContext.insertAfter(inlineableBody); * Assumes, that resultExpression == null, when result is not needed.
statementLevelContext.replaceMe(program.getEmptyStatement()); * @see FunctionInlineMutator.isResultNeeded()
*/
if (resultExpression == null) {
statementContext.removeCurrentStatement();
} else {
context.replaceMe(resultExpression);
}
context.replaceMe(resultExpression); statementContext.shiftCurrentStatementForward();
InsertionPoint<JsStatement> insertionPoint = statementContext.getInsertionPoint();
if (inlineableBody instanceof JsBlock) {
JsBlock block = (JsBlock) inlineableBody;
insertionPoint.insertAllAfter(block.getStatements());
} else {
insertionPoint.insertAfter(inlineableBody);
}
} }
@Nullable @NotNull
private JsFunction findDeclaration(@NotNull JsInvocation call) { private JsInliningContext getInliningContext() {
JsName name = getFunctionName(call); return inliningContexts.peek();
if (functions.containsKey(name)) { }
return functions.get(name);
}
if (name.getStaticRef() != null && name.getStaticRef() instanceof JsFunction) { @NotNull FunctionContext getFunctionContext() {
return (JsFunction) name.getStaticRef(); return getInliningContext().getFunctionContext();
}
return null;
} }
private boolean canInline(@NotNull JsInvocation call) { private boolean canInline(@NotNull JsInvocation call) {
return findDeclaration(call) != null; FunctionContext functionContext = getFunctionContext();
return functionContext.hasFunctionDefinition(call);
} }
private static boolean shouldInline(@NotNull JsInvocation call) { private static boolean shouldInline(@NotNull JsInvocation call) {
return call.getInlineStrategy().isInline(); return call.getInlineStrategy().isInline();
} }
@NotNull
private static JsName getFunctionName(@NotNull JsInvocation call) {
JsExpression qualifier = call.getQualifier();
assert qualifier instanceof JsNameRef;
JsName name = ((JsNameRef) qualifier).getName();
assert name != null;
return name;
}
} }