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 functionsToEnter: Set<JsFunction>
val invocationsToSkip: Set<JsInvocation> 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.backend.ast.*
import org.jetbrains.kotlin.js.dce.Context.Node 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.inline.util.collectLocalVariables
import org.jetbrains.kotlin.js.translate.context.Namer import org.jetbrains.kotlin.js.translate.context.Namer
@@ -30,6 +31,7 @@ class Analyzer(private val context: Context) : JsVisitor() {
private val invocationsToSkip = mutableSetOf<JsInvocation>() private val invocationsToSkip = mutableSetOf<JsInvocation>()
val moduleMapping = mutableMapOf<JsStatement, String>() val moduleMapping = mutableMapOf<JsStatement, String>()
private val functionsToEnter = mutableSetOf<JsFunction>() private val functionsToEnter = mutableSetOf<JsFunction>()
private val functionsToSkip = mutableSetOf<Context.Node>()
val analysisResult = object : AnalysisResult { val analysisResult = object : AnalysisResult {
override val nodeMap: Map<JsNode, Node> get() = this@Analyzer.nodeMap 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 functionsToEnter: Set<JsFunction> get() = this@Analyzer.functionsToEnter
override val invocationsToSkip: Set<JsInvocation> get() = this@Analyzer.invocationsToSkip override val invocationsToSkip: Set<JsInvocation> get() = this@Analyzer.invocationsToSkip
override val functionsToSkip: Set<Context.Node> get() = this@Analyzer.functionsToSkip
} }
override fun visitVars(x: JsVars) { override fun visitVars(x: JsVars) {
@@ -87,13 +91,13 @@ class Analyzer(private val context: Context) : JsVisitor() {
} }
} }
// Object.defineProperty()
when { when {
// Object.defineProperty()
context.isObjectDefineProperty(function) -> context.isObjectDefineProperty(function) ->
handleObjectDefineProperty(x, expression.arguments.getOrNull(0), expression.arguments.getOrNull(1), handleObjectDefineProperty(x, expression.arguments.getOrNull(0), expression.arguments.getOrNull(1),
expression.arguments.getOrNull(2)) expression.arguments.getOrNull(2))
// Kotlin.defineModule() // Kotlin.defineModule()
context.isDefineModule(function) -> context.isDefineModule(function) ->
// (just remove it) // (just remove it)
astNodesToEliminate += x astNodesToEliminate += x
@@ -227,13 +231,25 @@ class Analyzer(private val context: Context) : JsVisitor() {
return leftNode 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) { if (context.isDefineInlineFunction(function) && rhs.arguments.size == 2) {
leftNode.functions += rhs.arguments[1] as JsFunction tryExtractFunction(rhs.arguments[1])?.let { (inlineableFunction, additionalDeps) ->
val defineInlineFunctionNode = context.extractNode(function) leftNode.functions += inlineableFunction
if (defineInlineFunctionNode != null) { val defineInlineFunctionNode = context.extractNode(function)
leftNode.dependencies += defineInlineFunctionNode if (defineInlineFunctionNode != null) {
leftNode.dependencies += defineInlineFunctionNode
}
leftNode.dependencies += additionalDeps
return leftNode
} }
}
tryExtractFunction(rhs)?.let { (functionBody, additionalDeps) ->
leftNode.functions += functionBody
leftNode.dependencies += additionalDeps
return leftNode return leftNode
} }
} }
@@ -247,7 +263,7 @@ class Analyzer(private val context: Context) : JsVisitor() {
val reassignValue = reassignment.arg2 val reassignValue = reassignment.arg2
if (reassignNode == secondNode && reassignNode != null && reassignValue is JsObjectLiteral && if (reassignNode == secondNode && reassignNode != null && reassignValue is JsObjectLiteral &&
reassignValue.propertyInitializers.isEmpty() reassignValue.propertyInitializers.isEmpty()
) { ) {
return processAssignment(node, lhs, rhs.arg1) return processAssignment(node, lhs, rhs.arg1)
} }
} }
@@ -278,6 +294,31 @@ class Analyzer(private val context: Context) : JsVisitor() {
return null 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?) { private fun handleObjectCreate(target: Node, arg: JsExpression?) {
if (arg == null) return if (arg == null) return
@@ -135,15 +135,18 @@ class ReachabilityTracker(
} }
} }
for (expr in node.functions) { if (node !in analysisResult.functionsToSkip) {
reportAndNest("traverse: function", expr) { for (expr in node.functions) {
expr.collectLocalVariables().let { reportAndNest("traverse: function", expr) {
context.addNodesForLocalVars(it) expr.collectLocalVariables().let {
context.namesOfLocalVars += it context.addNodesForLocalVars(it)
context.namesOfLocalVars += it
}
withErasedThis { expr.body.accept(this) }
} }
withErasedThis { expr.body.accept(this) }
} }
} }
for (expr in node.expressions) { for (expr in node.expressions) {
reportAndNest("traverse: value", expr) { reportAndNest("traverse: value", expr) {
expr.accept(this) 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.isDefineInlineFunction(function: JsExpression): Boolean = isKotlinFunction(function, "defineInlineFunction")
fun Context.isWrapFunction(function: JsExpression): Boolean = isKotlinFunction(function, "wrapFunction")
fun Context.isObjectFunction(function: JsExpression, functionName: String): Boolean { fun Context.isObjectFunction(function: JsExpression, functionName: String): Boolean {
if (function !is JsNameRef) return false if (function !is JsNameRef) return false
if (function.ident != functionName) 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.dce.InputFile
import org.jetbrains.kotlin.js.facade.* import org.jetbrains.kotlin.js.facade.*
import org.jetbrains.kotlin.js.parser.parse 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.SourceFilePathResolver
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
import org.jetbrains.kotlin.incremental.js.TranslationResultValue import org.jetbrains.kotlin.incremental.js.TranslationResultValue
@@ -1,3 +1,4 @@
// EXPECTED_REACHABLE_NODES: 997
var global = "" var global = ""
fun log(message: String) { fun log(message: String) {
@@ -1,3 +1,4 @@
// EXPECTED_REACHABLE_NODES: 996
var global = "" var global = ""
fun log(message: String) { fun log(message: String) {
@@ -1,3 +1,4 @@
// EXPECTED_REACHABLE_NODES: 999
// MODULE: lib // MODULE: lib
// FILE: lib.kt // FILE: lib.kt
@@ -1,3 +1,4 @@
// EXPECTED_REACHABLE_NODES: 996
// MODULE: lib // MODULE: lib
// FILE: lib.kt // FILE: lib.kt
fun baz(x: String) = "($x)" fun baz(x: String) = "($x)"
@@ -1,3 +1,4 @@
// EXPECTED_REACHABLE_NODES: 992
// MODULE: lib // MODULE: lib
// FILE: lib.kt // FILE: lib.kt
fun foo() = "OK" fun foo() = "OK"
@@ -1,3 +1,4 @@
// EXPECTED_REACHABLE_NODES: 994
// MODULE: lib1 // MODULE: lib1
// FILE: lib1.kt // FILE: lib1.kt
package lib1 package lib1
@@ -1,4 +1,4 @@
// EXPECTED_REACHABLE_NODES: 509 // EXPECTED_REACHABLE_NODES: 1012
// MODULE: lib // MODULE: lib
// FILE: lib.kt // 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=publishedTopLevel_61zpoe$ count=1
// PROPERTY_WRITE_COUNT: name=published_61zpoe$ count=1 // PROPERTY_WRITE_COUNT: name=published_61zpoe$ count=1
// PROPERTY_WRITE_COUNT: name=B count=1 // PROPERTY_WRITE_COUNT: name=B count=1