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;
import com.google.dart.compiler.backend.js.ast.*;
import org.jetbrains.annotations.NotNull;
import java.util.*;
class FunctionInlineMutator {
private static final String RESULT_LABEL = "result_inlined";
private final JsInvocation call;
private final JsScope callerScope;
private JsBlock body;
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(
JsInvocation call,
JsScope callerScope,
JsFunction invokedFunction
@NotNull JsInvocation call,
@NotNull InliningContext inliningContext
) {
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) {
this.call = call;
this.callerScope = callerScope;
this.invokedFunction = invokedFunction;
private FunctionInlineMutator(@NotNull JsInvocation call, @NotNull InliningContext inliningContext) {
FunctionContext functionContext = inliningContext.getFunctionContext();
invokedFunction = functionContext.getFunctionDefinition(call);
body = invokedFunction.getBody().deepCopy();
}
private InlineableResult process() {
JsBlock inlinedBody = new JsBlock(statements);
JsNameRef result = callerScope.declareName(RESULT_LABEL).makeRef();
statements.addAll(invokedFunction.getBody().getStatements());
return new InlineableResult(inlinedBody, result);
private void process() {
}
}
@@ -27,31 +27,79 @@ import static org.jetbrains.k2js.inline.FunctionInlineMutator.getInlineableCallR
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 Stack<JsScope> scopeStack = new Stack<JsScope>();
private final JsProgram program;
private final Stack<JsInliningContext> inliningContexts = new Stack<JsInliningContext>();
public static JsProgram process(JsProgram program) {
IdentityHashMap<JsName, JsFunction> functions = FunctionCollector.collectFunctions(program);
JsInliner inliner = new JsInliner(program, functions);
return inliner.process();
JsInliner inliner = new JsInliner(functions);
return inliner.accept(program);
}
JsInliner(JsProgram program, IdentityHashMap<JsName, JsFunction> functions) {
this.program = program;
JsInliner(IdentityHashMap<JsName, JsFunction> functions) {
this.functions = functions;
}
@Override
public boolean visit(JsFunction function, JsContext context) {
scopeStack.push(function.getScope());
inliningContexts.push(new JsInliningContext(function));
return super.visit(function, context);
}
@Override
public void endVisit(JsFunction function, JsContext context) {
super.endVisit(function, context);
scopeStack.pop();
inliningContexts.pop();
}
@Override
@@ -66,65 +114,52 @@ public class JsInliner extends JsVisitorWithContextImpl {
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) {
JsFunction functionToInline = findDeclaration(call);
assert functionToInline != null;
JsContext statementLevelContext = getLastStatementLevelContext();
assert statementLevelContext != null;
JsScope currentScope = getCurrentFunctionScope();
InlineableResult inlineableResult = getInlineableCallReplacement(call, currentScope, functionToInline);
JsInliningContext inliningContext = getInliningContext();
FunctionContext functionContext = getFunctionContext();
functionContext.declareFunctionConstructorCalls(call.getArguments());
InlineableResult inlineableResult = getInlineableCallReplacement(call, inliningContext);
JsStatement inlineableBody = inlineableResult.getInlineableBody();
JsExpression resultExpression = inlineableResult.getResultExpression();
StatementContext statementContext = inliningContext.getStatementContext();
statementLevelContext.insertAfter(statementLevelContext.getCurrentNode());
statementLevelContext.insertAfter(inlineableBody);
statementLevelContext.replaceMe(program.getEmptyStatement());
/**
* Assumes, that resultExpression == null, when result is not needed.
* @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
private JsFunction findDeclaration(@NotNull JsInvocation call) {
JsName name = getFunctionName(call);
if (functions.containsKey(name)) {
return functions.get(name);
}
@NotNull
private JsInliningContext getInliningContext() {
return inliningContexts.peek();
}
if (name.getStaticRef() != null && name.getStaticRef() instanceof JsFunction) {
return (JsFunction) name.getStaticRef();
}
return null;
@NotNull FunctionContext getFunctionContext() {
return getInliningContext().getFunctionContext();
}
private boolean canInline(@NotNull JsInvocation call) {
return findDeclaration(call) != null;
FunctionContext functionContext = getFunctionContext();
return functionContext.hasFunctionDefinition(call);
}
private static boolean shouldInline(@NotNull JsInvocation call) {
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;
}
}