[JS IR] Perform optimizations on the generated JS code

The patch adopts and reuses the optimizations from the legacy backend.

The optimizations remove useless temporary variables,
statements and simplify generated JS code.

The optimizations can be disabled by `-Xoptimize-generated-js=false`.

Related to KT-51139
This commit is contained in:
Alexander Korepanov
2023-04-11 20:04:28 +02:00
committed by Space Team
parent 84b5af3c89
commit 79d378f2bd
87 changed files with 173 additions and 224 deletions
@@ -121,4 +121,6 @@ public class JSConfigurationKeys {
public static final CompilerConfigurationKey<ZipFileSystemAccessor> ZIP_FILE_SYSTEM_ACCESSOR =
CompilerConfigurationKey.create("zip file system accessor, used for klib reading");
public static final CompilerConfigurationKey<Boolean> OPTIMIZE_GENERATED_JS =
CompilerConfigurationKey.create("perform additional optimizations on the generated JS code");
}
@@ -74,7 +74,7 @@ internal class DoWhileGuardElimination(private val root: JsStatement) {
else -> null
}
if (guard != null) {
if (guard != null && guard.statement !is JsLoop) {
// When do..while loop has no label and we encounter `break guard` from nested loop, we can't
// replace this break with continue. Example:
@@ -88,6 +88,16 @@ import org.jetbrains.kotlin.js.translate.utils.splitToRanges
* foo(B, $a)
*
* we get `$a` eliminated.
*
* It is also worth taking care of the temporary variables captured into closure as they cannot be simply removed.
*
* function test(a) {
* var tmp_a = a // removing this temporary variable changes function behaviour
* var f = function() { console.log(tmp_a) }
* a = []
* return f
* }
*
*/
internal class TemporaryVariableElimination(private val function: JsFunction) {
private val root = function.body
@@ -95,6 +105,7 @@ internal class TemporaryVariableElimination(private val function: JsFunction) {
private val usages = mutableMapOf<JsName, Int>()
private val definedValues = mutableMapOf<JsName, JsExpression>()
private val temporary = mutableSetOf<JsName>()
private val capturedInClosure = mutableSetOf<JsName>()
private var hasChanges = false
private val localVariables = function.collectLocalVariables()
@@ -201,6 +212,7 @@ internal class TemporaryVariableElimination(private val function: JsFunction) {
for (freeVar in x.collectFreeVariables()) {
useVariable(freeVar)
useVariable(freeVar)
capturedInClosure += freeVar
}
}
@@ -260,9 +272,12 @@ internal class TemporaryVariableElimination(private val function: JsFunction) {
namesWithSideEffects += name
}
}
}
else if (sideEffects) {
invalidateTemporaries()
} else {
if (sideEffects) {
invalidateTemporaries()
} else {
invalidateTemporariesUsingName(name)
}
}
}
@@ -359,6 +374,21 @@ internal class TemporaryVariableElimination(private val function: JsFunction) {
lastAssignedVars.clear()
}
private fun invalidateTemporariesUsingName(name: JsName) {
lastAssignedVars.removeAll { (_, expr) ->
var nameUsed = false
object : RecursiveJsVisitor() {
override fun visitNameRef(nameRef: JsNameRef) {
if (nameRef.name == name) {
nameUsed = true
}
super.visitNameRef(nameRef)
}
}.accept(expr)
nameUsed
}
}
private fun handleExpression(expression: JsExpression): Boolean {
val candidateFinder = SubstitutionCandidateFinder()
candidateFinder.accept(expression)
@@ -593,7 +623,9 @@ internal class TemporaryVariableElimination(private val function: JsFunction) {
(definitions[name] ?: 0) > 0 && (usages[name] ?: 0) == 0 && name in temporary && !name.imported
private fun shouldConsiderTemporary(name: JsName): Boolean {
if (definitions[name] != 1 || name !in temporary) return false
if (definitions[name] != 1 || name !in temporary || name in capturedInClosure) {
return false
}
val expr = definedValues[name]
// It's useful to copy trivial expressions when they are used more than once. Example are temporary variables
@@ -12,4 +12,4 @@ fun baz() = 1
fun bar() = 2
// LINES(JS): 1 3 3 2 2 4 3 4 4 4 5 5 2 8 8 10 10 10 12 12 12
// LINES(JS_IR): 1 2 8 * 3 4 5 * 8 8 10 10 10 10 12 12 12 12
// LINES(JS_IR): 1 2 8 * 4 3 4 5 * 8 8 10 10 10 10 12 12 12 12
@@ -14,5 +14,5 @@ private inline fun foo(): Int {
return 23
}
// LINES(JS): 1 10 3 3 3 4 3 6 13 13 3 14 2 12 15 13 13 14 14
// LINES(JS_IR): 1 1 * 3 4 6 * 8 * 13 13 14 14 8 2 12 12 13 13 14 14
// LINES(JS): 1 10 3 3 3 4 3 6 13 13 3 14 2 12 15 13 13 14 14
// LINES(JS_IR): 1 1 * 3 4 6 * 13 13 14 2 12 12 13 13 14 14
+1 -1
View File
@@ -15,4 +15,4 @@ suspend fun bar(): Unit {
}
// LINES(JS): 39 4 4 4 7 5 5 45 45 5 93 45 5 5 6 4 4 4 9 15 9 9 9 * 9 15 10 10 11 11 11 11 11 * 11 12 12 13 13 13 13 13 13 13 14 14 * 9 15 9 9 9 9
// LINES(JS_IR): 4 4 * 19 * 5 * 45 * 93 93 45 45 45 6 6 19 7 7 9 9 * 9 * 9 * 10 10 * 11 * 11 12 12 * 13 * 13 14 14 15 15
// LINES(JS_IR): 4 4 * 93 93 45 45 7 7 6 9 9 * 9 * 9 * 10 10 * 11 * 11 12 12 * 13 * 13 14 14 15 15
@@ -33,4 +33,4 @@ inline operator fun P.component1() = a
inline operator fun P.component2() = b
// LINES(JS): 15 22 17 17 31 18 18 33 20 20 21 21 22 22 3 23 9 9 9 9 4 9 9 9 6 6 31 7 7 33 11 11 12 12 15 15 25 27 26 26 29 29 29 * 31 31 31 33 33 33 * 1 * 1
// LINES(JS_IR): 1 1 1 1 1 1 1 1 * 3 3 9 9 4 9 * 6 * 31 31 6 * 7 * 33 33 7 11 11 12 12 15 15 25 25 26 26 29 29 29 29 29 29 29 29 29 29 29 29 31 31 31 31 33 33 33 33 15 16 15 * 17 * 31 31 17 * 18 * 33 33 18 20 20 21 21 22 22 * 1
// LINES(JS_IR): 1 1 1 1 1 1 1 1 * 3 3 9 9 4 9 * 6 31 * 7 33 11 11 12 12 15 15 25 25 26 26 29 29 29 29 29 29 29 29 29 29 29 29 31 31 31 31 33 33 33 33 15 16 15 * 17 31 * 18 33 20 20 21 21 22 22 * 1
+1 -1
View File
@@ -10,4 +10,4 @@ fun box(x: String?) {
fun foo() = "bar"
// LINES(JS): 3 8 7 7 4 4 4 5 5 7 7 7 7 * 5 4 5 10 10 10 * 1 * 1
// LINES(JS_IR): 1 1 1 1 1 1 1 1 * 3 3 4 5 * 5 5 * 7 7 7 * 5 4 4 10 10 10 10 * 1
// LINES(JS_IR): 1 1 1 1 1 1 1 1 * 3 3 4 * 5 5 * 7 7 7 * 5 4 4 10 10 10 10 * 1
+2 -2
View File
@@ -17,5 +17,5 @@ fun box() {
}
}
// LINES(JS): 1 18 2 2 10 2 2 2 2 2 2 3 3 6 6 6 6 7 7 10 10 10 10 10 10 11 11 14 14 15 15 15 15 16 16
// LINES(JS_IR): 1 1 * 2 * 35 * 18 * 12 2 18 18 35 35 2 2 2 2 2 2 2 2 3 3 6 6 6 6 6 6 6 7 7 10 10 10 10 11 11 14 15 15 15 15 15 15 15 15 16 16
// LINES(JS): 1 18 2 2 10 2 2 2 2 2 2 3 3 6 6 6 6 7 7 10 10 10 10 10 10 11 11 14 14 15 15 15 15 16 16
// LINES(JS_IR): 1 1 * 2 2 2 2 2 2 2 2 3 3 6 6 6 6 6 6 6 7 7 10 10 10 10 11 11 14 15 15 15 15 15 15 15 15 16 16
+2 -2
View File
@@ -8,5 +8,5 @@ fun foo(x: Int) {
println(y)
}
// LINES(JS): 1 9 2 2 3 3 4 4 5 5 6 6 7 7 8 8
// LINES(JS_IR): 1 1 2 3 3 3 4 4 5 5 6 6 7 7 8 8
// LINES(JS): 1 9 2 2 3 3 4 4 5 5 6 6 7 7 8 8
// LINES(JS_IR): 1 1 2 3 3 4 4 5 5 6 6 7 7 8 8
+1 -1
View File
@@ -12,4 +12,4 @@ inline fun foo(x: Int) {
fun bar() = 23
// LINES(JS): 3 5 4 4 8 8 9 9 7 10 8 8 9 9 12 12 12 * 1 * 1
// LINES(JS_IR): 1 1 1 1 1 1 1 1 * 3 3 4 * 4 8 8 9 9 7 7 8 8 9 9 12 12 12 12 * 1
// LINES(JS_IR): 1 1 1 1 1 1 1 1 * 3 3 * 4 8 8 9 9 7 7 8 8 9 9 12 12 12 12 * 1
@@ -10,4 +10,4 @@ fun bar() {
}
// LINES(JS): 1 1 1 1 1 6 2 2 3 3 4 4 8 10 2 2 9 2 3 3 4 4
// LINES(JS_IR): 1 1 2 3 3 3 4 4 8 8 9 * 2 3 3 3 4 4
// LINES(JS_IR): 1 1 2 3 3 3 4 4 8 8 * 2 3 3 3 4 4
@@ -22,5 +22,5 @@ fun box() {
foo("42")
}
// LINES(JS): 6 20 23 7 7 21 7 8 8 21 8 7 7 22 7 8 8 22 8
// LINES(JS_IR): 20 20 21 * 7 7 8 8 22 * 7 7 8 8
// LINES(JS): 6 20 23 7 7 21 7 8 8 21 8 7 7 22 7 8 8 22 8
// LINES(JS_IR): 20 20 * 7 7 8 8 * 7 7 8 8
+2 -2
View File
@@ -11,5 +11,5 @@ inline fun bar() {
println("bar2")
}
// LINES(JS): 1 7 2 2 10 10 11 11 4 4 10 10 11 11 6 6 9 9 9 9 9 12 10 10 11 11
// LINES(JS_IR): 1 1 2 2 3 * 10 10 11 11 4 4 5 * 10 10 11 11 6 6 9 9 10 10 11 11
// LINES(JS): 1 7 2 2 10 10 11 11 4 4 10 10 11 11 6 6 9 9 9 9 9 12 10 10 11 11
// LINES(JS_IR): 1 1 2 2 * 10 10 11 11 4 4 * 10 10 11 11 6 6 9 9 10 10 11 11
@@ -16,5 +16,5 @@ inline fun foo(f: () -> Unit) {
println("after")
}
// LINES(JS): 1 11 2 2 14 14 4 4 16 16 6 6 14 14 8 8 16 16 10 10 13 13 13 13 13 17 14 14 15 15 16 16
// LINES(JS_IR): 1 1 2 2 3 * 14 14 15 * 4 4 16 16 6 6 7 * 14 14 15 * 8 8 16 16 10 10 13 13 14 14 15 15 16 16
// LINES(JS): 1 11 2 2 14 14 4 4 16 16 6 6 14 14 8 8 16 16 10 10 13 13 13 13 13 17 14 14 15 15 16 16
// LINES(JS_IR): 1 1 2 2 * 14 14 * 4 4 16 16 6 6 * 14 14 * 8 8 16 16 10 10 13 13 14 14 15 15 16 16
+2 -2
View File
@@ -11,5 +11,5 @@ inline fun foo(): Boolean {
return true
}
// LINES(JS): 3 7 4 4 4 10 10 4 11 4 5 5 9 12 10 10 11 11 * 1 * 1
// LINES(JS_IR): 1 1 1 1 1 1 1 1 * 3 3 * 4 4 * 4 * 10 10 11 11 4 4 4 5 5 9 9 10 10 11 11 * 1
// LINES(JS): 3 7 4 4 4 10 10 4 11 4 5 5 9 12 10 10 11 11 * 1 * 1
// LINES(JS_IR): 1 1 1 1 1 1 1 1 * 3 3 * 4 4 * 10 10 11 4 4 4 5 5 9 9 10 10 11 11 * 1
@@ -24,5 +24,5 @@ fun baz() = "baz"
fun boo() = "boo"
// LINES(JS): 1 17 9 4 6 6 7 7 3 3 3 * 13 12 13 13 14 19 21 20 20 23 23 23 25 25 25
// LINES(JS_IR): 1 1 * 2 * 20 * 4 * 6 7 * 3 20 20 * 11 * 20 * 12 12 13 14 20 20 19 19 20 20 23 23 23 23 25 25 25 25
// LINES(JS): 1 17 9 4 6 6 7 7 3 3 3 * 13 12 13 13 14 19 21 20 20 23 23 23 25 25 25
// LINES(JS_IR): 1 1 * 4 * 6 7 * 13 12 13 19 19 20 20 23 23 23 23 25 25 25 25
@@ -19,4 +19,4 @@ fun box(x: Int) {
}
// LINES(JS): 1 19 4 4 3 3 4 6 9 9 6 11 13 13 11 16 16 3 2
// LINES(JS_IR): 1 1 4 2 8 7 6 7 8 9 12 11 12 13 16
// LINES(JS_IR): 1 1 2 8 7 6 4 6 7 4 7 8 4 8 9 12 11 4 11 12 4 12 13 16
@@ -29,4 +29,4 @@ fun four() = 4
fun five() = 5
// LINES(JS): 1 19 4 4 3 6 4 6 4 7 4 8 9 9 11 4 11 4 12 13 13 16 16 2 21 21 21 23 23 23 25 25 25 27 27 27 29 29 29
// LINES(JS_IR): 1 1 4 2 8 7 6 7 8 9 12 11 12 13 16 21 21 21 21 23 23 23 23 25 25 25 25 27 27 27 27 29 29 29 29
// LINES(JS_IR): 1 1 2 8 7 6 4 6 7 4 7 8 4 8 9 12 11 4 11 12 4 12 13 16 21 21 21 21 23 23 23 23 25 25 25 25 27 27 27 27 29 29 29 29