From 87d108e75ed2a59744af9b723b3494515cce030d Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov Date: Wed, 30 Jul 2014 19:40:23 +0400 Subject: [PATCH] JS inline: default parameter processing for inlined functions When function is inlined initializers for default parameters can be: 1. removed if argument is not `void 0` 2. expanded otherwise --- .../compiler/backend/js/ast/JsParameter.java | 9 ++ .../k2js/inline/DefaultParameters.kt | 143 ++++++++++++++++++ .../k2js/inline/FunctionInlineMutator.java | 15 +- .../expression/FunctionTranslator.java | 4 +- 4 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 js/js.inliner/src/org/jetbrains/k2js/inline/DefaultParameters.kt diff --git a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsParameter.java b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsParameter.java index c1328666bb0..0b7859b82f3 100644 --- a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsParameter.java +++ b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/JsParameter.java @@ -13,6 +13,7 @@ import org.jetbrains.annotations.NotNull; public final class JsParameter extends SourceInfoAwareJsNode implements HasName { @NotNull private final JsName name; + private boolean hasDefaultValue = false; public JsParameter(@NotNull JsName name) { this.name = name; @@ -30,6 +31,14 @@ public final class JsParameter extends SourceInfoAwareJsNode implements HasName return name; } + public boolean hasDefaultValue() { + return hasDefaultValue; + } + + public void setHasDefaultValue(boolean hasDefaultValue) { + this.hasDefaultValue = hasDefaultValue; + } + @Override public void accept(JsVisitor v) { v.visitParameter(this); diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/DefaultParameters.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/DefaultParameters.kt new file mode 100644 index 00000000000..c44fa4e71a5 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/DefaultParameters.kt @@ -0,0 +1,143 @@ +/* + * 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 java.util.IdentityHashMap +import java.util.Collections + +/** + * Removes initializers for default parameters with non-void0 arguments given + * Expands initializers for default parameters with void0 arguments given + * + * @see isInitializer + */ +fun removeRedundantDefaultInitializers(arguments: List, parameters: List, body: JsBlock) { + val toRemove = getInitializedParametersNames(arguments, parameters) + val toExpand = getUnInitializedParametersNames(arguments, parameters) + + val statements = body.getStatements() + + val newStatements = statements.flatMap { + when { + !isInitializer(it) -> listOf(it) + else -> { + val name = getNameFromInitializer(it) + when { + name in toRemove.keySet() -> listOf() + name in toExpand.keySet() -> expand(it) + else -> listOf(it) + } + } + } + }.filterNotNull() + + statements.clear() + statements.addAll(newStatements) +} + +/** + * Tests if statement is an initializer for parameter with default value. + * + * This check assumes that default parameter initializer is generated like: + * if (defaultParam === void 0) { + * ... + * } + */ +private fun isInitializer(statement: JsStatement): Boolean { + return getNameFromInitializer(statement) != null +} + +private fun getNameFromInitializer(statement: JsStatement): JsName? { + val jsIf = (statement as? JsIf) + val ifExpr = jsIf?.getIfExpression() as? JsBinaryOperation + + return when { + jsIf?.getElseStatement() != null -> null + else -> getNameFromInitializer(ifExpr) + } +} + +private fun getNameFromInitializer(initializerTestExpression: JsExpression?): JsName? { + val binOp = initializerTestExpression as? JsBinaryOperation + val arg1 = binOp?.getArg1() + val arg2 = binOp?.getArg2() + val operator = binOp?.getOperator() + + return when { + operator != JsBinaryOperator.REF_EQ -> null + !isVoid0(arg2) -> null + else -> (arg1 as? JsNameRef)?.getName() + } +} + +private fun isVoid0(expr: JsExpression?): Boolean { + val op = (expr as? JsUnaryOperation)?.getOperator() + return op == JsUnaryOperator.VOID +} + +private fun getInitializedParametersNames(args: List, + params: List): Map { + + val names = IdentityHashMap() + val argParams = (args zip params).stream() + + val initialized = argParams.filter { it.second.hasDefaultValue() } + .filter { !isVoid0(it.first) } + .map { it.second } + + initialized.map { it.getName() } + .forEach { names.put(it, true) } + + return names +} + +private fun getUnInitializedParametersNames(args: List, + params: List): Map { + + val names = IdentityHashMap() + val argParams = (args zip params).stream() + + val void0Params = argParams.filter { it.second.hasDefaultValue() } + .filter { isVoid0(it.first) } + .map { it.second } + + val noArgsParams = params.drop(args.size).stream() + val uninitialized = void0Params.plus(noArgsParams) + + uninitialized.map { it.getName() } + .forEach { names.put(it, true) } + + return names +} + +/** + * Changes initializer check: + * if (arg === void 0) { arg = ... } + * To list of statement(s): + * arg = ... + */ +private fun expand(initializer: JsStatement): Iterable { + val then = (initializer as? JsIf)?.getThenStatement() + + return when { + then is JsBlock -> then.getStatements() + then != null -> listOf(then) + else -> listOf() + } +} \ No newline at end of file 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 1a387ac568f..efea056851e 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionInlineMutator.java +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionInlineMutator.java @@ -20,13 +20,10 @@ 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.needToAlias; -import static org.jetbrains.k2js.inline.InlinePackage.aliasArgumentsIfNeeded; -import static org.jetbrains.k2js.inline.InlinePackage.renameLocals; -import static org.jetbrains.k2js.inline.InlinePackage.replaceThisReference; -import static org.jetbrains.k2js.inline.InlinePackage.hasThisReference; +import static org.jetbrains.k2js.inline.InlinePackage.*; -import java.util.*; +import java.util.Collection; +import java.util.List; class FunctionInlineMutator { private static final String BREAK_LABEL = "break_inlined"; @@ -77,8 +74,12 @@ class FunctionInlineMutator { } private void process() { + List arguments = getArguments(); + List parameters = getParameters(); + replaceThis(); - aliasArgumentsIfNeeded(renamingContext, getArguments(), getParameters()); + removeRedundantDefaultInitializers(arguments, parameters, body); + aliasArgumentsIfNeeded(renamingContext, arguments, parameters); renameLocals(renamingContext, invokedFunction); applyRenaming(); replaceReturns(); diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java index cf5e483262e..e6285662ad8 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/FunctionTranslator.java @@ -130,7 +130,9 @@ public final class FunctionTranslator extends AbstractTranslator { public static void addParameters(List list, FunctionDescriptor descriptor, TranslationContext context) { for (ValueParameterDescriptor valueParameter : descriptor.getValueParameters()) { - list.add(new JsParameter(context.getNameForDescriptor(valueParameter))); + JsParameter jsParameter = new JsParameter(context.getNameForDescriptor(valueParameter)); + jsParameter.setHasDefaultValue(valueParameter.hasDefaultValue()); + list.add(jsParameter); } }