Support new inline function format in JS DCE tool

This commit is contained in:
Alexey Andreev
2017-07-12 17:04:50 +03:00
parent 901346243d
commit b90885d1cb
13 changed files with 74 additions and 17 deletions
@@ -31,4 +31,6 @@ interface AnalysisResult {
val functionsToEnter: Set<JsFunction>
val invocationsToSkip: Set<JsInvocation>
val functionsToSkip: Set<Context.Node>
}
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.dce
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.dce.Context.Node
import org.jetbrains.kotlin.js.inline.util.collectDefinedNames
import org.jetbrains.kotlin.js.inline.util.collectLocalVariables
import org.jetbrains.kotlin.js.translate.context.Namer
@@ -30,6 +31,7 @@ class Analyzer(private val context: Context) : JsVisitor() {
private val invocationsToSkip = mutableSetOf<JsInvocation>()
val moduleMapping = mutableMapOf<JsStatement, String>()
private val functionsToEnter = mutableSetOf<JsFunction>()
private val functionsToSkip = mutableSetOf<Context.Node>()
val analysisResult = object : AnalysisResult {
override val nodeMap: Map<JsNode, Node> get() = this@Analyzer.nodeMap
@@ -41,6 +43,8 @@ class Analyzer(private val context: Context) : JsVisitor() {
override val functionsToEnter: Set<JsFunction> get() = this@Analyzer.functionsToEnter
override val invocationsToSkip: Set<JsInvocation> get() = this@Analyzer.invocationsToSkip
override val functionsToSkip: Set<Context.Node> get() = this@Analyzer.functionsToSkip
}
override fun visitVars(x: JsVars) {
@@ -87,13 +91,13 @@ class Analyzer(private val context: Context) : JsVisitor() {
}
}
// Object.defineProperty()
when {
// Object.defineProperty()
context.isObjectDefineProperty(function) ->
handleObjectDefineProperty(x, expression.arguments.getOrNull(0), expression.arguments.getOrNull(1),
expression.arguments.getOrNull(2))
// Kotlin.defineModule()
// Kotlin.defineModule()
context.isDefineModule(function) ->
// (just remove it)
astNodesToEliminate += x
@@ -227,13 +231,25 @@ class Analyzer(private val context: Context) : JsVisitor() {
return leftNode
}
// lhs = Kotlin.defineInlineFunction('fqn', function() { ... })
// lhs = Kotlin.defineInlineFunction('fqn', <function declaration>)
// where <function declaration> is one of
// - function() { ... }
// - wrapFunction(function() { ... })
if (context.isDefineInlineFunction(function) && rhs.arguments.size == 2) {
leftNode.functions += rhs.arguments[1] as JsFunction
val defineInlineFunctionNode = context.extractNode(function)
if (defineInlineFunctionNode != null) {
leftNode.dependencies += defineInlineFunctionNode
tryExtractFunction(rhs.arguments[1])?.let { (inlineableFunction, additionalDeps) ->
leftNode.functions += inlineableFunction
val defineInlineFunctionNode = context.extractNode(function)
if (defineInlineFunctionNode != null) {
leftNode.dependencies += defineInlineFunctionNode
}
leftNode.dependencies += additionalDeps
return leftNode
}
}
tryExtractFunction(rhs)?.let { (functionBody, additionalDeps) ->
leftNode.functions += functionBody
leftNode.dependencies += additionalDeps
return leftNode
}
}
@@ -247,7 +263,7 @@ class Analyzer(private val context: Context) : JsVisitor() {
val reassignValue = reassignment.arg2
if (reassignNode == secondNode && reassignNode != null && reassignValue is JsObjectLiteral &&
reassignValue.propertyInitializers.isEmpty()
) {
) {
return processAssignment(node, lhs, rhs.arg1)
}
}
@@ -278,6 +294,31 @@ class Analyzer(private val context: Context) : JsVisitor() {
return null
}
private fun tryExtractFunction(expression: JsExpression): Pair<JsFunction, List<Context.Node>>? {
when (expression) {
is JsFunction -> return Pair(expression, emptyList())
is JsInvocation -> {
if (context.isWrapFunction(expression.qualifier)) {
(expression.arguments.getOrNull(0) as? JsFunction)?.let { wrapper ->
val statementsWithoutBody = wrapper.body.statements.filter { it !is JsReturn }
JsBlock(statementsWithoutBody).let {
context.addNodesForLocalVars(collectDefinedNames(it))
accept(it)
}
val wrapperNode = context.extractNode(expression.qualifier)?.also {
functionsToSkip += it
}
val body = wrapper.body.statements.filterIsInstance<JsReturn>().first().expression as JsFunction
return Pair(body, listOfNotNull(wrapperNode))
}
}
}
}
return null
}
private fun handleObjectCreate(target: Node, arg: JsExpression?) {
if (arg == null) return
@@ -135,15 +135,18 @@ class ReachabilityTracker(
}
}
for (expr in node.functions) {
reportAndNest("traverse: function", expr) {
expr.collectLocalVariables().let {
context.addNodesForLocalVars(it)
context.namesOfLocalVars += it
if (node !in analysisResult.functionsToSkip) {
for (expr in node.functions) {
reportAndNest("traverse: function", expr) {
expr.collectLocalVariables().let {
context.addNodesForLocalVars(it)
context.namesOfLocalVars += it
}
withErasedThis { expr.body.accept(this) }
}
withErasedThis { expr.body.accept(this) }
}
}
for (expr in node.expressions) {
reportAndNest("traverse: value", expr) {
expr.accept(this)
@@ -27,6 +27,8 @@ fun Context.isDefineModule(function: JsExpression): Boolean = isKotlinFunction(f
fun Context.isDefineInlineFunction(function: JsExpression): Boolean = isKotlinFunction(function, "defineInlineFunction")
fun Context.isWrapFunction(function: JsExpression): Boolean = isKotlinFunction(function, "wrapFunction")
fun Context.isObjectFunction(function: JsExpression, functionName: String): Boolean {
if (function !is JsNameRef) return false
if (function.ident != functionName) return false
@@ -44,7 +44,10 @@ import org.jetbrains.kotlin.js.dce.DeadCodeElimination
import org.jetbrains.kotlin.js.dce.InputFile
import org.jetbrains.kotlin.js.facade.*
import org.jetbrains.kotlin.js.parser.parse
import org.jetbrains.kotlin.js.parser.sourcemaps.*
import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapError
import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapLocationRemapper
import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapParser
import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapSuccess
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
import org.jetbrains.kotlin.incremental.js.TranslationResultValue
@@ -1,3 +1,4 @@
// EXPECTED_REACHABLE_NODES: 997
var global = ""
fun log(message: String) {
@@ -1,3 +1,4 @@
// EXPECTED_REACHABLE_NODES: 996
var global = ""
fun log(message: String) {
@@ -1,3 +1,4 @@
// EXPECTED_REACHABLE_NODES: 999
// MODULE: lib
// FILE: lib.kt
@@ -1,3 +1,4 @@
// EXPECTED_REACHABLE_NODES: 996
// MODULE: lib
// FILE: lib.kt
fun baz(x: String) = "($x)"
@@ -1,3 +1,4 @@
// EXPECTED_REACHABLE_NODES: 992
// MODULE: lib
// FILE: lib.kt
fun foo() = "OK"
@@ -1,3 +1,4 @@
// EXPECTED_REACHABLE_NODES: 994
// MODULE: lib1
// FILE: lib1.kt
package lib1
@@ -1,4 +1,4 @@
// EXPECTED_REACHABLE_NODES: 509
// EXPECTED_REACHABLE_NODES: 1012
// MODULE: lib
// FILE: lib.kt
@@ -1,4 +1,4 @@
// EXPECTED_REACHABLE_NODES: 1695
// EXPECTED_REACHABLE_NODES: 990
// PROPERTY_WRITE_COUNT: name=publishedTopLevel_61zpoe$ count=1
// PROPERTY_WRITE_COUNT: name=published_61zpoe$ count=1
// PROPERTY_WRITE_COUNT: name=B count=1