From 78e7b9c32bd48624c1bdb711f0e63d31fc71d474 Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov Date: Fri, 12 Sep 2014 11:00:24 +0400 Subject: [PATCH] JS inline: replace returns with result variable assignment and break --- .../k2js/inline/FunctionInlineMutator.java | 55 ++++++++++++- .../k2js/inline/ReplaceReturnVisitor.java | 78 +++++++++++++++++++ .../jetbrains/k2js/inline/ReturnCounter.java | 44 +++++++++++ .../k2js/inline/util/sideEffectUtils.kt | 63 +++++++++++++++ 4 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 js/js.inliner/src/org/jetbrains/k2js/inline/ReplaceReturnVisitor.java create mode 100644 js/js.inliner/src/org/jetbrains/k2js/inline/ReturnCounter.java create mode 100644 js/js.inliner/src/org/jetbrains/k2js/inline/util/sideEffectUtils.kt diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionInlineMutator.java b/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionInlineMutator.java index 5f39070e644..769bb890523 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionInlineMutator.java +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionInlineMutator.java @@ -17,19 +17,27 @@ package org.jetbrains.k2js.inline; import com.google.dart.compiler.backend.js.ast.*; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import static org.jetbrains.k2js.inline.InlinePackage.aliasArgumentsIfNeeded; +import static org.jetbrains.k2js.inline.InlinePackage.renameLocals; import java.util.*; class FunctionInlineMutator { + private static final String BREAK_LABEL = "break_inlined"; + private static final String RESULT_LABEL = "result_inlined"; + private JsBlock body; + private final boolean isResultNeeded; private final JsFunction invokedFunction; private final List arguments; private final List parameters; private final RenamingContext renamingContext; private final InsertionPoint insertionPoint; + private JsNameRef resultExpr = null; + private JsLabel breakLabel = null; public static InlineableResult getInlineableCallReplacement( @NotNull JsInvocation call, @@ -39,13 +47,24 @@ class FunctionInlineMutator { mutator.process(); JsStatement inlineableBody = mutator.body; - return new InlineableResult(inlineableBody, call); + if (mutator.breakLabel != null) { + mutator.breakLabel.setStatement(inlineableBody); + inlineableBody = mutator.breakLabel; + } + + JsExpression resultExpression = null; + if (mutator.isResultNeeded) { + resultExpression = mutator.resultExpr; + } + + return new InlineableResult(inlineableBody, resultExpression); } private FunctionInlineMutator(@NotNull JsInvocation call, @NotNull InliningContext inliningContext) { FunctionContext functionContext = inliningContext.getFunctionContext(); invokedFunction = functionContext.getFunctionDefinition(call); body = invokedFunction.getBody().deepCopy(); + isResultNeeded = inliningContext.isResultNeeded(call); arguments = call.getArguments(); parameters = invokedFunction.getParameters(); renamingContext = inliningContext.getRenamingContext(); @@ -56,6 +75,7 @@ class FunctionInlineMutator { aliasArgumentsIfNeeded(renamingContext, arguments, parameters); renameLocals(renamingContext, invokedFunction); applyRenaming(); + replaceReturns(); } private void applyRenaming() { @@ -64,4 +84,37 @@ class FunctionInlineMutator { Collection declarations = renamingResult.getDeclarations(); insertionPoint.insertAllBefore(declarations); } + + private void replaceReturns() { + int returnCount = ReturnCounter.countReturns(body); + if (returnCount == 0) { + // TODO return Unit (KT-5647) + resultExpr = JsLiteral.UNDEFINED; + } else { + doReplaceReturns(returnCount); + } + } + + private void doReplaceReturns(int returnCount) { + JsReturn returnOnTop = ContainerUtil.findInstance(body.getStatements(), JsReturn.class); + boolean hasReturnOnTopLevel = returnOnTop != null; + + if (isResultNeeded) { + JsName resultName = renamingContext.getFreshName(getResultLabel()); + renamingContext.newVar(resultName, null); + resultExpr = resultName.makeRef(); + } + + boolean needBreakLabel = !(returnCount == 1 && hasReturnOnTopLevel); + JsNameRef breakLabelRef = null; + + if (needBreakLabel) { + JsName breakName = renamingContext.getFreshName(getBreakLabel()); + breakLabelRef = breakName.makeRef(); + breakLabel = new JsLabel(breakName); + } + + assert resultExpr == null || resultExpr instanceof JsNameRef; + ReplaceReturnVisitor.replaceReturn(body, (JsNameRef) resultExpr, breakLabelRef); + } } diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/ReplaceReturnVisitor.java b/js/js.inliner/src/org/jetbrains/k2js/inline/ReplaceReturnVisitor.java new file mode 100644 index 00000000000..2b0b4d8aaf6 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/ReplaceReturnVisitor.java @@ -0,0 +1,78 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.k2js.inline; + +import com.google.dart.compiler.backend.js.ast.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.k2js.translate.utils.JsAstUtils; + +import static org.jetbrains.k2js.inline.util.UtilPackage.canHaveSideEffect; + +class ReplaceReturnVisitor extends JsVisitorWithContextImpl { + private final JsNameRef resultRef; + + @Nullable + private final JsNameRef breakLabel; + + @NotNull + public static JsNode replaceReturn(JsStatement statement, @Nullable JsNameRef resultRef, @Nullable JsNameRef breakLabel) { + return new ReplaceReturnVisitor(resultRef, breakLabel).accept(statement); + } + + private ReplaceReturnVisitor(@Nullable JsNameRef resultRef, @Nullable JsNameRef breakLabel) { + this.resultRef = resultRef; + this.breakLabel = breakLabel; + } + + @Override + public void endVisit(JsReturn x, JsContext ctx) { + if (breakLabel != null) { + JsBreak breakFunction = new JsBreak(breakLabel); + ctx.insertAfter(breakFunction); + } + + JsExpression returnExpression = x.getExpression(); + if (returnExpression == null) { + ctx.removeMe(); + return; + } + + if (resultRef != null) { + JsExpression resultAssignment = JsAstUtils.assignment(resultRef, returnExpression); + ctx.replaceMe(new JsExpressionStatement(resultAssignment)); + return; + } + + if (canHaveSideEffect(returnExpression)) { + JsStatement replacement = new JsExpressionStatement(returnExpression); + ctx.replaceMe(replacement); + } else { + ctx.removeMe(); + } + } + + @Override + public boolean visit(JsObjectLiteral x, JsContext ctx) { + return false; + } + + @Override + public boolean visit(JsFunction x, JsContext ctx) { + return false; + } +} diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/ReturnCounter.java b/js/js.inliner/src/org/jetbrains/k2js/inline/ReturnCounter.java new file mode 100644 index 00000000000..79bc4457db6 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/ReturnCounter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.k2js.inline; + +import com.google.dart.compiler.backend.js.ast.JsNode; +import com.google.dart.compiler.backend.js.ast.JsReturn; +import com.google.dart.compiler.backend.js.ast.RecursiveJsVisitor; + +public class ReturnCounter extends RecursiveJsVisitor { + + private int count = 0; + + public static int countReturns(JsNode root) { + ReturnCounter counter = new ReturnCounter(); + root.accept(counter); + + return counter.getCount(); + } + + ReturnCounter() {} + + private int getCount() { + return count; + } + + @Override + public void visitReturn(JsReturn x) { + count++; + } +} diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/util/sideEffectUtils.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/util/sideEffectUtils.kt new file mode 100644 index 00000000000..1931acd9515 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/util/sideEffectUtils.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.k2js.inline.util + +import com.google.dart.compiler.backend.js.ast.JsExpression +import com.google.dart.compiler.backend.js.ast.RecursiveJsVisitor +import com.google.dart.compiler.backend.js.ast.JsNode +import com.google.dart.compiler.backend.js.ast.JsBinaryOperation +import com.google.dart.compiler.backend.js.ast.JsPostfixOperation +import com.google.dart.compiler.backend.js.ast.JsPrefixOperation +import com.google.dart.compiler.backend.js.ast.JsNew +import com.google.dart.compiler.backend.js.ast.JsInvocation + +public fun canHaveSideEffect(expression: JsExpression): Boolean { + val visitor = SideEffectExpessionVisitor() + visitor.accept(expression) + return visitor.canHaveSideEffect +} + +private class SideEffectExpessionVisitor() : RecursiveJsVisitor() { + public var canHaveSideEffect: Boolean = false + private set + + override fun visitElement(node: JsNode?) { + if (!canHaveSideEffect) { + super.visitElement(node) + } + } + + override fun visitBinaryExpression(x: JsBinaryOperation?) { + canHaveSideEffect = true + } + + override fun visitPostfixOperation(x: JsPostfixOperation?) { + canHaveSideEffect = true + } + + override fun visitPrefixOperation(x: JsPrefixOperation?) { + canHaveSideEffect = true + } + + override fun visitNew(x: JsNew?) { + canHaveSideEffect = true + } + + override fun visitInvocation(invocation: JsInvocation?) { + canHaveSideEffect = true + } +} \ No newline at end of file