From 5aaa186180125bad58ac1166c143667151219220 Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov Date: Fri, 8 Aug 2014 17:41:35 +0400 Subject: [PATCH] JS inline: remove unused function literals and local functions --- .../org/jetbrains/k2js/inline/JsInliner.java | 5 +- .../k2js/inline/clean/FunctionRemover.kt | 34 ++++++ .../k2js/inline/clean/LocalFunctions.kt | 104 ++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 js/js.inliner/src/org/jetbrains/k2js/inline/clean/FunctionRemover.kt create mode 100644 js/js.inliner/src/org/jetbrains/k2js/inline/clean/LocalFunctions.kt diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/JsInliner.java b/js/js.inliner/src/org/jetbrains/k2js/inline/JsInliner.java index 18336d71cfc..0fbe2bc3cf8 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/JsInliner.java +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/JsInliner.java @@ -25,6 +25,7 @@ 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.FunctionInlineMutator.getInlineableCallReplacement; public class JsInliner extends JsVisitorWithContextImpl { @@ -146,7 +147,9 @@ public class JsInliner extends JsVisitorWithContextImpl { public static JsProgram process(JsProgram program) { IdentityHashMap functions = FunctionCollector.collectFunctions(program); JsInliner inliner = new JsInliner(functions); - return inliner.accept(program); + inliner.accept(program); + removeUnusedLocalFunctions(program, functions); + return program; } JsInliner(IdentityHashMap functions) { diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/FunctionRemover.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/FunctionRemover.kt new file mode 100644 index 00000000000..cd6bc3cd4ff --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/FunctionRemover.kt @@ -0,0 +1,34 @@ +/* + * 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 = listOf()) : NodeRemovingVisitor(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) + } + } +} diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/LocalFunctions.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/LocalFunctions.kt new file mode 100644 index 00000000000..507d857a913 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/LocalFunctions.kt @@ -0,0 +1,104 @@ +/* + * 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 com.intellij.util.containers.Stack +import java.util.IdentityHashMap + +public fun removeUnusedLocalFunctions(root: JsNode, functions: Map) { + val removable = with(UnusedLocalFunctionsCollector(functions)) { + process() + accept(root) + removableFunctions + } + + with(FunctionRemover(removable)) { + accept(root) + } +} + +private class UnusedLocalFunctionsCollector(functions: Map) : JsVisitorWithContextImpl() { + private val tracker = ReferenceTracker() + private val functions = functions + private val processed = IdentitySet() + + public val removableFunctions: List + get() = tracker.removable + + public fun process() { + functions.filter { it.value.isLocal() } + .forEach { tracker.addCandidateForRemoval(it.key, it.value) } + + for ((name, function) in functions) { + if (function.isLocal()) { + processLocalFunction(name, function) + } else { + processNonLocalFunction(name, function) + } + + processed.add(function) + } + } + + override fun visit(x: JsPropertyInitializer?, ctx: JsContext?): Boolean { + val value = x?.getValueExpr() + + return when (value) { + is JsFunction -> !wasProcessed(value) + else -> super.visit(x, ctx) + } + } + + override fun visit(x: JsFunction?, ctx: JsContext?): Boolean { + return !(wasProcessed(x)) + } + + override fun endVisit(x: JsFunction?, ctx: JsContext?) { + if (x == null) return + + processed.add(x) + } + + override fun endVisit(x: JsNameRef?, ctx: JsContext?) { + val name = x?.getName() + if (isFunctionReference(x) && name != null) { + tracker.markReachable(name) + } + } + + private fun processLocalFunction(name: JsName, function: JsFunction) { + for (referenced in collectFunctionReferencesInside(function)) { + tracker.addRemovableReference(name, referenced) + } + } + + private fun processNonLocalFunction(name: JsName, function: JsFunction) { + for (referenced in collectFunctionReferencesInside(function)) { + tracker.markReachable(referenced) + } + } + + private fun isFunctionReference(nameRef: HasName?): Boolean { + return nameRef?.getName()?.getStaticRef() is JsFunction + } + + private fun wasProcessed(function: JsFunction?): Boolean = function in processed +} +