JS inline refactor: refactored clean utils after code review

This commit is contained in:
Alexey Tsvetkov
2014-10-02 15:32:16 +04:00
committed by Zalim Bashorov
parent 1ad090e3a7
commit 80eca3e49e
11 changed files with 180 additions and 284 deletions
@@ -1,143 +0,0 @@
/*
* 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<JsExpression>, parameters: List<JsParameter>, 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<JsStatement>()
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<JsExpression>,
params: List<JsParameter>): Map<JsName, Boolean> {
val names = IdentityHashMap<JsName, Boolean>()
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<JsExpression>,
params: List<JsParameter>): Map<JsName, Boolean> {
val names = IdentityHashMap<JsName, Boolean>()
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<JsStatement> {
val then = (initializer as? JsIf)?.getThenStatement()
return when {
then is JsBlock -> then.getStatements()
then != null -> listOf(then)
else -> listOf()
}
}
@@ -23,6 +23,7 @@ import org.jetbrains.k2js.inline.context.*;
import static org.jetbrains.k2js.inline.util.UtilPackage.collectInstances;
import static org.jetbrains.k2js.inline.util.UtilPackage.replaceReturns;
import static org.jetbrains.k2js.inline.util.UtilPackage.replaceThisReference;
import static org.jetbrains.k2js.inline.clean.CleanPackage.removeDefaultInitializers;
import static org.jetbrains.k2js.inline.InlinePackage.*;
@@ -78,7 +79,7 @@ class FunctionInlineMutator {
List<JsParameter> parameters = getParameters();
replaceThis();
removeRedundantDefaultInitializers(arguments, parameters, body);
removeDefaultInitializers(arguments, parameters, body);
aliasArgumentsIfNeeded(namingContext, arguments, parameters);
renameLocalNames(namingContext, invokedFunction);
@@ -27,8 +27,8 @@ import java.util.Set;
import java.util.Stack;
import java.util.List;
import static org.jetbrains.k2js.inline.clean.CleanPackage.removeUnusedLocalFunctionInstances;
import static org.jetbrains.k2js.inline.clean.CleanPackage.removeUnusedLocalFunctions;
import static org.jetbrains.k2js.inline.clean.CleanPackage.removeUnusedLocalFunctionDeclarations;
import static org.jetbrains.k2js.inline.clean.CleanPackage.removeUnusedFunctionDefinitions;
import static org.jetbrains.k2js.inline.FunctionInlineMutator.getInlineableCallReplacement;
import static org.jetbrains.k2js.inline.util.UtilPackage.IdentitySet;
import static org.jetbrains.k2js.inline.util.UtilPackage.collectNamedFunctions;
@@ -74,7 +74,7 @@ public class JsInliner extends JsVisitorWithContextImpl {
IdentityHashMap<JsName, JsFunction> functions = collectNamedFunctions(program);
JsInliner inliner = new JsInliner(functions);
inliner.accept(program);
removeUnusedLocalFunctions(program, functions);
removeUnusedFunctionDefinitions(program, functions);
return program;
}
@@ -95,7 +95,7 @@ public class JsInliner extends JsVisitorWithContextImpl {
@Override
public void endVisit(JsFunction function, JsContext context) {
super.endVisit(function, context);
removeUnusedLocalFunctionInstances(function);
removeUnusedLocalFunctionDeclarations(function);
processedFunctions.add(function);
assert inProcessFunctions.contains(function);
@@ -1,34 +0,0 @@
/*
* 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.clean
import com.google.dart.compiler.backend.js.ast.*
import org.jetbrains.k2js.inline.util.IdentitySet
private class FunctionRemover(removable: Collection<JsFunction> = listOf()) : NodeRemovingVisitor<JsFunction>(removable) {
override fun endVisit(x: JsPropertyInitializer?, ctx: JsContext?) {
if (x == null) return
val value = x.getValueExpr()
if (value is JsFunction && shouldRemove(value)) {
ctx?.removeMe()
} else {
super.endVisit(x, ctx)
}
}
}
@@ -16,17 +16,24 @@
package org.jetbrains.k2js.inline.clean
import com.google.dart.compiler.backend.js.ast.*
import com.google.dart.compiler.backend.js.ast.JsVisitorWithContextImpl
import com.google.dart.compiler.backend.js.ast.JsNode
import com.google.dart.compiler.backend.js.ast.JsContext
private class GenericRemover<T : JsNode>(removable: Collection<T> = listOf()) : NodeRemovingVisitor<T>(removable) {
private class NodeRemover<T>(val klass: Class<T>, val predicate: (T) -> Boolean): JsVisitorWithContextImpl() {
override fun <T : JsNode?> doTraverse(node: T?, ctx: JsContext?) {
if (node == null) return
if (node == null || ctx == null) return
if (shouldRemove(node)) {
ctx?.removeMe()
} else {
super.doTraverse(node, ctx)
if (klass.isInstance(node)) {
val instance = klass.cast(node)!!
if (predicate(instance)) {
ctx.removeMe()
return
}
}
super.doTraverse(node, ctx)
}
}
@@ -1,31 +0,0 @@
/*
* 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.clean
import com.google.dart.compiler.backend.js.ast.*
import org.jetbrains.k2js.inline.util.IdentitySet
import java.util.IdentityHashMap
import org.jetbrains.k2js.inline.util.toIdentitySet
private abstract class NodeRemovingVisitor<T : JsNode>(removable: Collection<T>) : JsVisitorWithContextImpl() {
private val removeSet: Set<T> = removable.toIdentitySet()
protected fun <R : JsNode?> shouldRemove(node: R): Boolean {
return removeSet.contains(node)
}
}
@@ -25,7 +25,7 @@ import java.util.ArrayList
private class ReferenceTracker<Reference, RemoveCandidate : JsNode> {
private val reachable = IdentityHashMap<Reference, Boolean>()
private val removableCandidates = IdentityHashMap<Reference, RemoveCandidate>()
private val refereceFromTo = IdentityHashMap<Reference, MutableList<Reference>>()
private val refereceFromTo = IdentityHashMap<Reference, MutableSet<Reference>>()
private val visited = IdentitySet<Reference>()
public val removable: List<RemoveCandidate>
@@ -65,8 +65,8 @@ private class ReferenceTracker<Reference, RemoveCandidate : JsNode> {
reachable[reference] = true
}
private fun getReferencedBy(referer: Reference): MutableList<Reference> {
return refereceFromTo.getOrPut(referer, { ArrayList<Reference>() })
private fun getReferencedBy(referer: Reference): MutableSet<Reference> {
return refereceFromTo.getOrPut(referer, { IdentitySet<Reference>() })
}
private fun isKnown(ref: Reference): Boolean {
@@ -1,50 +0,0 @@
/*
* 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.clean
import com.google.dart.compiler.backend.js.ast.*
import org.jetbrains.k2js.inline.util.IdentitySet
import java.util.ArrayList
private fun collectFunctionReferencesInside(scope: JsNode): List<JsName> {
return with(ReferenceNameCollector()) {
accept(scope)
references.filter { it.getStaticRef() is JsFunction }
}
}
private fun collectReferencesInside(scope: JsNode): List<JsName> {
return with(ReferenceNameCollector()) {
accept(scope)
references
}
}
private class ReferenceNameCollector : JsVisitorWithContextImpl() {
private val referenceSet = IdentitySet<JsName>()
public val references: List<JsName>
get() = referenceSet.toList()
override fun endVisit(x: JsNameRef?, ctx: JsContext?) {
val name = x?.getName()
if (name != null) {
referenceSet.add(name)
}
}
}
@@ -0,0 +1,126 @@
/*
* 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.clean
import com.google.dart.compiler.backend.js.ast.*
import org.jetbrains.k2js.inline.util.toIdentitySet
import org.jetbrains.k2js.inline.util.zipWithDefault
import org.jetbrains.k2js.translate.context.Namer
import org.jetbrains.k2js.translate.context.Namer.isUndefined
import org.jetbrains.k2js.translate.utils.JsAstUtils.flattenStatement
import java.util.IdentityHashMap
import java.util.Collections
import org.jetbrains.k2js.translate.utils.JsAstUtils
/**
* Removes initializers for default parameters with defined arguments given
* Expands initializers for default parameters with undefined arguments given
*
* @see isInitializer
*/
public fun removeDefaultInitializers(arguments: List<JsExpression>, parameters: List<JsParameter>, body: JsBlock) {
val toRemove = getDefaultParamsNames(arguments, parameters, initialized = true)
val toExpand = getDefaultParamsNames(arguments, parameters, initialized = false)
val statements = body.getStatements()
val newStatements = statements.flatMap {
val name = getNameFromInitializer(it)
if (name != null && !isNameInitialized(name, it)) {
throw AssertionError("Unexpected initializer structure")
}
when {
name in toRemove ->
listOf<JsStatement>()
name in toExpand ->
flattenStatement((it as JsIf).getThenStatement()!!)
else ->
listOf(it)
}
}
statements.clear()
statements.addAll(newStatements)
}
private fun getNameFromInitializer(statement: JsStatement): JsName? {
val ifStmt = (statement as? JsIf)
val testExpr = ifStmt?.getIfExpression()
val elseStmt = ifStmt?.getElseStatement()
if (elseStmt == null && testExpr is JsBinaryOperation)
return getNameFromInitializer(testExpr)
return null
}
private fun getNameFromInitializer(isInitializedExpr: JsBinaryOperation): JsName? {
val arg1 = isInitializedExpr.getArg1()
val arg2 = isInitializedExpr.getArg2()
val op = isInitializedExpr.getOperator()
if (arg1 == null || arg2 == null || op == null) {
return null
}
if (op == JsBinaryOperator.REF_EQ && isUndefined(arg2)) {
return (arg1 as? JsNameRef)?.getName()
}
return null
}
/**
* Tests if the last statement of initializer
* is name assignment.
*/
private fun isNameInitialized(
name: JsName,
initializer: JsStatement
): Boolean {
val thenStmt = (initializer as JsIf).getThenStatement()!!
val lastThenStmt = flattenStatement(thenStmt).last
val expr = (lastThenStmt as? JsExpressionStatement)?.getExpression()
if (expr !is JsBinaryOperation) return false
val op = expr.getOperator()!!
if (!op.isAssignment()) return false
val arg1 = expr.getArg1()
if (arg1 is HasName && arg1.getName() identityEquals name) return true
return false
}
private fun getDefaultParamsNames(
args: List<JsExpression>,
params: List<JsParameter>,
initialized: Boolean
): Set<JsName> {
val argsParams = args.zipWithDefault(params, Namer.UNDEFINED_EXPRESSION)
val relevantParams = argsParams.stream()
.filter { it.second.hasDefaultValue() }
.filter { initialized == !isUndefined(it.first) }
val names = relevantParams.map { it.second.getName() }
return names.toIdentitySet()
}
@@ -21,17 +21,28 @@ import org.jetbrains.k2js.inline.util.IdentitySet
import com.intellij.util.containers.Stack
import java.util.IdentityHashMap
import org.jetbrains.k2js
import org.jetbrains.k2js.inline.util.collectReferencesInside
import org.jetbrains.k2js.inline.util.collectFunctionReferencesInside
public fun removeUnusedLocalFunctions(root: JsNode, functions: Map<JsName, JsFunction>) {
/**
* Removes unused function definitions:
* f: function() { return 10 }
*
* At now, it only removes unused local functions and function literals,
* because named functions can be referenced from another module.
*/
public fun removeUnusedFunctionDefinitions(root: JsNode, functions: Map<JsName, JsFunction>) {
val removable = with(UnusedLocalFunctionsCollector(functions)) {
process()
accept(root)
removableFunctions
}
with(FunctionRemover(removable)) {
accept(root)
}
NodeRemover(javaClass<JsPropertyInitializer>()) {
val function = it.getValueExpr() as? JsFunction
function in removable
}.accept(root)
}
private class UnusedLocalFunctionsCollector(functions: Map<JsName, JsFunction>) : JsVisitorWithContextImpl() {
@@ -20,27 +20,36 @@ import com.google.dart.compiler.backend.js.ast.*
import java.util.ArrayList
import java.util.IdentityHashMap
import org.jetbrains.k2js
public fun removeUnusedLocalFunctionInstances(root: JsNode) {
/**
* Removes unused local function declarations like:
* var inc = _.foo.f$inc(a)
*
* Declaration can become unused, if inlining happened.
*/
public fun removeUnusedLocalFunctionDeclarations(root: JsNode) {
val removable =
with(UnusedInstanceCollector()) {
accept(root)
removableInstances
removableDeclarations
}
GenericRemover(removable).accept(root)
NodeRemover(javaClass<JsStatement>()) {
it in removable
}.accept(root)
}
private class UnusedInstanceCollector : JsVisitorWithContextImpl() {
private val tracker = ReferenceTracker<JsName, JsStatement>()
public val removableInstances: List<JsStatement>
public val removableDeclarations: List<JsStatement>
get() = tracker.removable
override fun visit(x: JsVars.JsVar?, ctx: JsContext?): Boolean {
if (x == null) return false
if (!isLocalFunctionInstance(x)) return super.visit(x, ctx)
if (!isLocalFunctionDeclaration(x)) return super.visit(x, ctx)
val name = x.getName()!!
val statementContext = getLastStatementLevelContext()
@@ -49,7 +58,7 @@ private class UnusedInstanceCollector : JsVisitorWithContextImpl() {
val currentStatement = currentNode as JsStatement
tracker.addCandidateForRemoval(name, currentStatement)
val references = collectReferencesInside(x)
val references = k2js.inline.util.collectReferencesInside(x)
references.filterNotNull()
.forEach { tracker.addRemovableReference(name, it) }
@@ -66,7 +75,7 @@ private class UnusedInstanceCollector : JsVisitorWithContextImpl() {
return false
}
private fun isLocalFunctionInstance(jsVar: JsVars.JsVar): Boolean {
private fun isLocalFunctionDeclaration(jsVar: JsVars.JsVar): Boolean {
val name = jsVar.getName()
val expr = jsVar.getInitExpression()
val staticRef = name?.getStaticRef()