From 524c475834152112a3d32aba460f03e74c3d645a Mon Sep 17 00:00:00 2001 From: Alexander Korepanov Date: Thu, 6 Jul 2023 17:24:28 +0200 Subject: [PATCH] [JS IR] Implement MoveTemporaryVariableDeclarationToAssignment optimization --- compiler/testData/debug/stepping/kt42208c.kt | 2 +- .../js/inline/clean/FunctionPostProcessor.kt | 3 +- ...emporaryVariableDeclarationToAssignment.kt | 202 ++++++++++++++++++ .../kotlin/js/test/handlers/JsAstHandler.kt | 7 +- ...raryVariableDeclarationToAssignmentTest.kt | 22 ++ .../js/testOld/utils/DirectiveTestUtils.java | 29 ++- .../js/test/fir/FirJsBoxTestGenerated.java | 16 ++ .../js/test/ir/IrBoxJsES6TestGenerated.java | 16 ++ .../js/test/ir/IrBoxJsTestGenerated.java | 16 ++ .../tempVarDeclOnAssignment.kt | 10 + .../tempVarDeclOnAssignmentTest.js | 16 ++ .../innerBlock.optimized.js | 105 +++++++++ .../innerBlock.original.js | 112 ++++++++++ .../notInitUsage.optimized.js | 58 +++++ .../notInitUsage.original.js | 58 +++++ .../sameBlock.optimized.js | 16 ++ .../sameBlock.original.js | 18 ++ .../siblingBlocks.optimized.js | 109 ++++++++++ .../siblingBlocks.original.js | 114 ++++++++++ .../ifWithoutElse.optimized.js | 6 +- .../transitiveChain.optimized.js | 12 +- .../assignmentToNonLocal.optimized.js | 5 +- .../testData/lineNumbers/coroutine.kt | 2 +- 23 files changed, 929 insertions(+), 25 deletions(-) create mode 100644 js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/MoveTemporaryVariableDeclarationToAssignment.kt create mode 100644 js/js.tests/test/org/jetbrains/kotlin/js/testOld/optimizer/MoveTemporaryVariableDeclarationToAssignmentTest.kt create mode 100644 js/js.translator/testData/box/jsAstOptimizations/tempVarDeclOnAssignment.kt create mode 100644 js/js.translator/testData/box/jsAstOptimizations/tempVarDeclOnAssignmentTest.js create mode 100644 js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/innerBlock.optimized.js create mode 100644 js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/innerBlock.original.js create mode 100644 js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/notInitUsage.optimized.js create mode 100644 js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/notInitUsage.original.js create mode 100644 js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/sameBlock.optimized.js create mode 100644 js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/sameBlock.original.js create mode 100644 js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/siblingBlocks.optimized.js create mode 100644 js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/siblingBlocks.original.js diff --git a/compiler/testData/debug/stepping/kt42208c.kt b/compiler/testData/debug/stepping/kt42208c.kt index 8174364f47d..99cb7521fbd 100644 --- a/compiler/testData/debug/stepping/kt42208c.kt +++ b/compiler/testData/debug/stepping/kt42208c.kt @@ -39,7 +39,7 @@ fun baz(v:(() -> Unit)) { // test.kt:8 box // EXPECTATIONS JS_IR -// test1.kt:12 box +// test1.kt:11 box // test.kt:5 box // test3.kt:15 baz // test1.kt:9 box$lambda diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/FunctionPostProcessor.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/FunctionPostProcessor.kt index 4ceacd3031b..32879ffc88f 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/FunctionPostProcessor.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/FunctionPostProcessor.kt @@ -31,7 +31,8 @@ class FunctionPostProcessor(val root: JsFunction) { { RedundantVariableDeclarationElimination(root.body).apply() }, { RedundantStatementElimination(root).apply() }, { CoroutineStateElimination(root.body).apply() }, - { BoxingUnboxingElimination(root.body).apply() } + { BoxingUnboxingElimination(root.body).apply() }, + { MoveTemporaryVariableDeclarationToAssignment(root.body).apply() } ) // TODO: reduce to A || B, A && B if possible diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/MoveTemporaryVariableDeclarationToAssignment.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/MoveTemporaryVariableDeclarationToAssignment.kt new file mode 100644 index 00000000000..2e06cbf3711 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/MoveTemporaryVariableDeclarationToAssignment.kt @@ -0,0 +1,202 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.js.inline.clean + +import org.jetbrains.kotlin.js.backend.ast.* +import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic +import org.jetbrains.kotlin.js.translate.utils.JsAstUtils +import java.util.LinkedHashSet + +/** + * Moving a declaration of the temporary variable without an initializer to the closest assignment. + * + * Basic example: + * var $tmp; + * $tmp = ; + * Transformed to: + * var $tmp = ; + * + * The optimization algorithm does the following: + * 1) Collects all temporary variables whose declarations do not have an initializer. + * 2) Collects the JsBlock set of all blocks where the temporary variables are assigned and used. + * 3) Ignores the temporary variable if it is used between the declaration and the first assignment. + * 4) Represents the collected JsBlocks as a tree. + * 5) Searches for the Lowest Common Ancestor (LCA) among all JsBlocks where the temporary variables are assigned or used. + * 6) If the LCA is found in the set of JsBlocks where the temporary variable is assigned, we can move the declaration to the assignment. + */ +class MoveTemporaryVariableDeclarationToAssignment(private val body: JsBlock) { + private var hasChanges = false + + private val varUsedInBlocks = hashMapOf>() + private val varAssignedInBlocks = hashMapOf>() + + private val blockParents = hashMapOf() + + private val varWithoutInitDeclarations = hashSetOf() + private val varBeforeAssignmentUsages = hashSetOf() + + private val removedVarDeclarations = hashSetOf() + + fun apply(): Boolean { + analyze() + perform() + + require(removedVarDeclarations.isEmpty()) + + return hasChanges + } + + private fun analyze() { + val visitor = object : RecursiveJsVisitor() { + private val blockStack = mutableListOf() + + private val currentBlock + get() = blockStack.last() + + override fun visitBlock(x: JsBlock) { + if (blockStack.isNotEmpty()) { + blockParents[x] = currentBlock + } + blockStack += x + super.visitBlock(x) + blockStack.removeLast() + } + + override fun visit(x: JsVars.JsVar) { + if (x.initExpression == null && x.synthetic) { + varWithoutInitDeclarations += x.name + } + super.visit(x) + } + + override fun visitVars(x: JsVars) { + if (x.synthetic) { + for (variable in x) { + if (variable.initExpression == null) { + varWithoutInitDeclarations += variable.name + } + } + } + super.visitVars(x) + } + + override fun visitNameRef(nameRef: JsNameRef) { + val name = nameRef.name + if (name != null && name in varWithoutInitDeclarations) { + varUsedInBlocks.getOrPut(name) { hashSetOf() } += currentBlock + + val assignments = varAssignedInBlocks[name] ?: emptySet() + val hasAssignment = blockStack.any { it in assignments } + if (!hasAssignment) { + varBeforeAssignmentUsages += name + } + } + super.visitNameRef(nameRef) + } + + override fun visitExpressionStatement(x: JsExpressionStatement) { + val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression) + if (assignment != null) { + val (name, _) = assignment + if (name in varWithoutInitDeclarations) { + varAssignedInBlocks.getOrPut(name) { hashSetOf() } += currentBlock + } + } + super.visitExpressionStatement(x) + } + } + + visitor.accept(body) + } + + private fun removeNonParentBlocks(block: JsBlock, parents: LinkedHashSet) { + var blockParent: JsBlock? = block + while (blockParent != null) { + if (blockParent !in parents) { + blockParent = blockParents[blockParent] + continue + } + val parentIter = parents.iterator() + while (parentIter.hasNext()) { + if (parentIter.next() != blockParent) { + parentIter.remove() + } else { + return + } + } + } + } + + private fun calculateLCA(blocks: Set): JsBlock { + require(blocks.isNotEmpty()) + + val parents = LinkedHashSet() + var firstBlockParent: JsBlock? = blocks.first() + while (firstBlockParent != null) { + parents += firstBlockParent + firstBlockParent = blockParents[firstBlockParent] + } + + for (block in blocks.asSequence().drop(1)) { + removeNonParentBlocks(block, parents) + } + + require(parents.isNotEmpty()) + return parents.first() + } + + private fun perform() { + val visitor = object : JsVisitorWithContextImpl() { + private fun canRemoveDeclarationWithoutInit(name: JsName): Boolean { + if (name !in varWithoutInitDeclarations || name in varBeforeAssignmentUsages) { + return false + } + + val assignedInBlocks = varAssignedInBlocks[name] ?: return false + val usedInBlocks = varUsedInBlocks[name] ?: emptySet() + + val lcaBlock = calculateLCA(usedInBlocks + assignedInBlocks) + return lcaBlock in assignedInBlocks + } + + override fun endVisit(x: JsVars.JsVar, ctx: JsContext<*>) { + if (canRemoveDeclarationWithoutInit(x.name)) { + removedVarDeclarations += x.name + ctx.removeMe() + hasChanges = true + } else { + super.endVisit(x, ctx) + } + } + + override fun endVisit(x: JsVars, ctx: JsContext<*>) { + if (x.isEmpty) { + ctx.removeMe() + hasChanges = true + } else { + super.endVisit(x, ctx) + } + } + + override fun endVisit(x: JsExpressionStatement, ctx: JsContext) { + val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression) + if (assignment != null) { + val (name, initExpr) = assignment + if (removedVarDeclarations.remove(name)) { + val varDeclarationWithInit = JsVars.JsVar(name, initExpr).apply { synthetic = true } + val vars = JsVars(varDeclarationWithInit).apply { synthetic = true } + ctx.replaceMe(vars) + } + accept(initExpr) + } else { + super.endVisit(x, ctx) + } + } + } + + visitor.accept(body) + } +} diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsAstHandler.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsAstHandler.kt index abd5c353c49..ea82a970683 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsAstHandler.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsAstHandler.kt @@ -17,12 +17,13 @@ import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.services.TestServices import org.jetbrains.kotlin.test.services.assertions import org.jetbrains.kotlin.test.services.isKtFile +import java.io.File class JsAstHandler(testServices: TestServices) : JsBinaryArtifactHandler(testServices) { override fun processAfterAllModules(someAssertionWasFailed: Boolean) {} override fun processModule(module: TestModule, info: BinaryArtifacts.Js) { - val ktFiles = module.files.filter { it.isKtFile }.map { it.originalContent } + val ktFiles = module.files.filter { it.isKtFile }.associate { it.originalFile to it.originalContent } val jsProgram = when (val artifact = info.unwrap()) { is BinaryArtifacts.Js.OldJsArtifact -> (artifact.translationResult as TranslationResult.Success).program is BinaryArtifacts.Js.JsIrArtifact -> artifact.compilerResult.outputs[TranslationMode.FULL_DEV]?.jsProgram ?: return @@ -31,8 +32,8 @@ class JsAstHandler(testServices: TestServices) : JsBinaryArtifactHandler(testSer processJsProgram(jsProgram, ktFiles, module.targetBackend!!) } - private fun processJsProgram(program: JsProgram, psiFiles: List, targetBackend: TargetBackend) { - psiFiles.forEach { DirectiveTestUtils.processDirectives(program, it, targetBackend) } + private fun processJsProgram(program: JsProgram, psiFiles: Map, targetBackend: TargetBackend) { + psiFiles.forEach { DirectiveTestUtils.processDirectives(program, it.key, it.value, targetBackend) } program.verifyAst(targetBackend) } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/optimizer/MoveTemporaryVariableDeclarationToAssignmentTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/optimizer/MoveTemporaryVariableDeclarationToAssignmentTest.kt new file mode 100644 index 00000000000..8ea065a5f83 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/optimizer/MoveTemporaryVariableDeclarationToAssignmentTest.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.js.testOld.optimizer + +import org.junit.Test + +class MoveTemporaryVariableDeclarationToAssignmentTest : BasicOptimizerTest("move-temporary-variable-declaration") { + @Test + fun sameBlock() = box() + + @Test + fun innerBlock() = box() + + @Test + fun siblingBlocks() = box() + + @Test + fun notInitUsage() = box() +} diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/utils/DirectiveTestUtils.java b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/utils/DirectiveTestUtils.java index ca606b50353..4546b0e08c8 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/utils/DirectiveTestUtils.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/utils/DirectiveTestUtils.java @@ -23,9 +23,11 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.js.backend.ast.*; import org.jetbrains.kotlin.js.inline.util.CollectUtilsKt; import org.jetbrains.kotlin.js.translate.expression.InlineMetadata; +import org.jetbrains.kotlin.test.KotlinTestUtils; import org.jetbrains.kotlin.test.TargetBackend; import org.junit.runners.model.MultipleFailureException; +import java.io.File; import java.util.*; import java.util.regex.Pattern; import java.util.regex.Matcher; @@ -97,6 +99,18 @@ public class DirectiveTestUtils { } }; + private static final DirectiveHandler EXPECT_GENERATED_JS = new DirectiveHandler("EXPECT_GENERATED_JS") { + @Override + void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception { + String functionName = arguments.getNamedArgument("function"); + String expected = arguments.getNamedArgument("expect"); + File expectedFile = new File(arguments.sourceFile.getParentFile(), expected); + String code = AstSearchUtil.getFunction(ast, functionName).toString(); + String msg = "Function '" + functionName + "' got different generated JS code"; + KotlinTestUtils.assertEqualsToFile(msg, expectedFile, code); + } + }; + private static final DirectiveHandler FUNCTION_EXISTS = new DirectiveHandler("CHECK_FUNCTION_EXISTS") { @Override void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception { @@ -481,6 +495,7 @@ public class DirectiveTestUtils { }; private static final List DIRECTIVE_HANDLERS = Arrays.asList( + EXPECT_GENERATED_JS, FUNCTION_CONTAINS_NO_CALLS, FUNCTION_NOT_CALLED, FUNCTION_CALLED_TIMES, @@ -515,12 +530,13 @@ public class DirectiveTestUtils { public static void processDirectives( @NotNull JsNode ast, + @NotNull File sourceFile, @NotNull String sourceCode, @NotNull TargetBackend targetBackend ) throws Exception { List assertionErrors = new ArrayList<>(); for (DirectiveHandler handler : DIRECTIVE_HANDLERS) { - handler.process(ast, sourceCode, targetBackend, assertionErrors); + handler.process(ast, sourceFile, sourceCode, targetBackend, assertionErrors); } MultipleFailureException.assertEmpty(assertionErrors); } @@ -651,13 +667,15 @@ public class DirectiveTestUtils { * * @see ArgumentsHelper for arguments format */ - void process(@NotNull JsNode ast, @NotNull String sourceCode, + void process(@NotNull JsNode ast, + @NotNull File sourceFile, + @NotNull String sourceCode, @NotNull TargetBackend targetBackend, List assertionErrors ) throws Exception { List directiveEntries = findLinesWithPrefixesRemoved(sourceCode, directive); for (String directiveEntry : directiveEntries) { - ArgumentsHelper arguments = new ArgumentsHelper(directiveEntry); + ArgumentsHelper arguments = new ArgumentsHelper(directiveEntry, sourceFile); if (!containsBackend(targetBackend, TARGET_BACKENDS, arguments, true) || containsBackend(targetBackend, IGNORED_BACKENDS, arguments, false)) { continue; @@ -696,9 +714,10 @@ public class DirectiveTestUtils { private final Map namedArguments = new HashMap<>(); private final String entry; private final Pattern argumentsPattern = Pattern.compile("[\\w$_;\\.]+(=((\".*?\")|[\\w$_;\\.]+))?"); - - ArgumentsHelper(@NotNull String directiveEntry) { + final File sourceFile; + ArgumentsHelper(@NotNull String directiveEntry, @NotNull File directiveSourceFile) { entry = directiveEntry; + sourceFile = directiveSourceFile; Matcher matcher = argumentsPattern.matcher(directiveEntry); diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java index dc225a7b227..8129868166a 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java @@ -6919,6 +6919,22 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest { } } + @Nested + @TestMetadata("js/js.translator/testData/box/jsAstOptimizations") + @TestDataPath("$PROJECT_ROOT") + public class JsAstOptimizations { + @Test + public void testAllFilesPresentInJsAstOptimizations() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/jsAstOptimizations"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @Test + @TestMetadata("tempVarDeclOnAssignment.kt") + public void testTempVarDeclOnAssignment() throws Exception { + runTest("js/js.translator/testData/box/jsAstOptimizations/tempVarDeclOnAssignment.kt"); + } + } + @Nested @TestMetadata("js/js.translator/testData/box/jsCode") @TestDataPath("$PROJECT_ROOT") diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java index cbd30f61780..ea9c37c952b 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java @@ -7025,6 +7025,22 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { } } + @Nested + @TestMetadata("js/js.translator/testData/box/jsAstOptimizations") + @TestDataPath("$PROJECT_ROOT") + public class JsAstOptimizations { + @Test + public void testAllFilesPresentInJsAstOptimizations() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/jsAstOptimizations"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true); + } + + @Test + @TestMetadata("tempVarDeclOnAssignment.kt") + public void testTempVarDeclOnAssignment() throws Exception { + runTest("js/js.translator/testData/box/jsAstOptimizations/tempVarDeclOnAssignment.kt"); + } + } + @Nested @TestMetadata("js/js.translator/testData/box/jsCode") @TestDataPath("$PROJECT_ROOT") diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index b52dd6d633b..ea6f1c1d7f3 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -6919,6 +6919,22 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { } } + @Nested + @TestMetadata("js/js.translator/testData/box/jsAstOptimizations") + @TestDataPath("$PROJECT_ROOT") + public class JsAstOptimizations { + @Test + public void testAllFilesPresentInJsAstOptimizations() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/jsAstOptimizations"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @Test + @TestMetadata("tempVarDeclOnAssignment.kt") + public void testTempVarDeclOnAssignment() throws Exception { + runTest("js/js.translator/testData/box/jsAstOptimizations/tempVarDeclOnAssignment.kt"); + } + } + @Nested @TestMetadata("js/js.translator/testData/box/jsCode") @TestDataPath("$PROJECT_ROOT") diff --git a/js/js.translator/testData/box/jsAstOptimizations/tempVarDeclOnAssignment.kt b/js/js.translator/testData/box/jsAstOptimizations/tempVarDeclOnAssignment.kt new file mode 100644 index 00000000000..84f1ea08130 --- /dev/null +++ b/js/js.translator/testData/box/jsAstOptimizations/tempVarDeclOnAssignment.kt @@ -0,0 +1,10 @@ +inline fun jsUpper(c: Char) = c.toString().asDynamic().toUpperCase().unsafeCast() + +inline fun upper(c: Char): Char = jsUpper(c)[0] + +// EXPECT_GENERATED_JS: function=test expect=tempVarDeclOnAssignmentTest.js +fun test(a: Char, b: Char): String { + return "${upper(a)}${upper(b)}" +} + +fun box() = test('o', 'k') diff --git a/js/js.translator/testData/box/jsAstOptimizations/tempVarDeclOnAssignmentTest.js b/js/js.translator/testData/box/jsAstOptimizations/tempVarDeclOnAssignmentTest.js new file mode 100644 index 00000000000..b6a64619cdf --- /dev/null +++ b/js/js.translator/testData/box/jsAstOptimizations/tempVarDeclOnAssignmentTest.js @@ -0,0 +1,16 @@ +function test(a, b) { + // Inline function 'upper' call + // Inline function 'jsUpper' call + // Inline function 'kotlin.js.unsafeCast' call + // Inline function 'kotlin.js.asDynamic' call + var tmp$ret$2 = toString(a).toUpperCase(); + var tmp$ret$3 = charSequenceGet(tmp$ret$2, 0); + var tmp = toString(tmp$ret$3); + // Inline function 'upper' call + // Inline function 'jsUpper' call + // Inline function 'kotlin.js.unsafeCast' call + // Inline function 'kotlin.js.asDynamic' call + var tmp$ret$6 = toString(b).toUpperCase(); + var tmp$ret$7 = charSequenceGet(tmp$ret$6, 0); + return tmp + toString(tmp$ret$7); +} diff --git a/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/innerBlock.optimized.js b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/innerBlock.optimized.js new file mode 100644 index 00000000000..9a20ea80c37 --- /dev/null +++ b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/innerBlock.optimized.js @@ -0,0 +1,105 @@ +function getArg(x) { + return x; +} + +function test1(o, k) { + if (o != k) { + var $tmp1 = getArg(k).toUpperCase(); + var $tmp2 = getArg(o).toUpperCase(); + + var O = getArg($tmp2); + var K = getArg($tmp1); + return O + K; + } +} + +function test2(o, k) { + if (o != k) { + var $tmp1 = getArg(k).toUpperCase(); + var $tmp2 = getArg(o).toUpperCase(); + + if (o) { + var O = getArg($tmp2); + var K = getArg($tmp1); + return O + K; + } + } +} + +function test3(o, k) { + if (o != k) { + var $tmp1 = getArg(k).toUpperCase(); + + if (o) { + var $tmp2 = getArg(o).toUpperCase(); + + var O = getArg($tmp2); + var K = getArg($tmp1); + return O + K; + } + } +} + +function test4(o, k) { + var $tmp1; + var $tmp2; + if (o != k) { + $tmp1 = getArg(k).toUpperCase(); + $tmp2 = getArg(o).toUpperCase(); + } + var O = getArg($tmp2); + var K = getArg($tmp1); + return O + K; +} + +function test5(ok) { + var $tmp; + if (ok) { + if (ok) { + if (ok) { + if (ok) { + $tmp = getArg(ok).toUpperCase(); + } + } + var OK = getArg($tmp); + } + } + return OK +} + +function test6(ok) { + if (ok) { + if (ok) { + var $tmp = 1 + if (ok) { + if (ok) { + $tmp = getArg(ok).toUpperCase(); + } + var OK = getArg($tmp); + } + } + } + return OK +} + +function box() { + if (test1("o", "k") != "OK") { + return "Fail test1" + } + if (test2("o", "k") != "OK") { + return "Fail test2" + } + if (test3("o", "k") != "OK") { + return "Fail test3" + } + if (test4("o", "k") != "OK") { + return "Fail test4" + } + if (test5("ok") != "OK") { + return "Fail test5" + } + if (test6("ok") != "OK") { + return "Fail test6" + } + return "OK" +} diff --git a/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/innerBlock.original.js b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/innerBlock.original.js new file mode 100644 index 00000000000..aea510a4cc1 --- /dev/null +++ b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/innerBlock.original.js @@ -0,0 +1,112 @@ +function getArg(x) { + return x; +} + +function test1(o, k) { + var $tmp1; + var $tmp2; + if (o != k) { + $tmp1 = getArg(k).toUpperCase(); + $tmp2 = getArg(o).toUpperCase(); + + var O = getArg($tmp2); + var K = getArg($tmp1); + return O + K; + } +} + +function test2(o, k) { + var $tmp1; + var $tmp2; + if (o != k) { + $tmp1 = getArg(k).toUpperCase(); + $tmp2 = getArg(o).toUpperCase(); + + if (o) { + var O = getArg($tmp2); + var K = getArg($tmp1); + return O + K; + } + } +} + +function test3(o, k) { + var $tmp1; + var $tmp2; + if (o != k) { + $tmp1 = getArg(k).toUpperCase(); + + if (o) { + $tmp2 = getArg(o).toUpperCase(); + + var O = getArg($tmp2); + var K = getArg($tmp1); + return O + K; + } + } +} + +function test4(o, k) { + var $tmp1; + var $tmp2; + if (o != k) { + $tmp1 = getArg(k).toUpperCase(); + $tmp2 = getArg(o).toUpperCase(); + } + var O = getArg($tmp2); + var K = getArg($tmp1); + return O + K; +} + +function test5(ok) { + var $tmp; + if (ok) { + if (ok) { + if (ok) { + if (ok) { + $tmp = getArg(ok).toUpperCase(); + } + } + var OK = getArg($tmp); + } + } + return OK +} + +function test6(ok) { + var $tmp; + if (ok) { + if (ok) { + $tmp = 1 + if (ok) { + if (ok) { + $tmp = getArg(ok).toUpperCase(); + } + var OK = getArg($tmp); + } + } + } + return OK +} + +function box() { + if (test1("o", "k") != "OK") { + return "Fail test1" + } + if (test2("o", "k") != "OK") { + return "Fail test2" + } + if (test3("o", "k") != "OK") { + return "Fail test3" + } + if (test4("o", "k") != "OK") { + return "Fail test4" + } + if (test5("ok") != "OK") { + return "Fail test5" + } + if (test6("ok") != "OK") { + return "Fail test6" + } + return "OK" +} diff --git a/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/notInitUsage.optimized.js b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/notInitUsage.optimized.js new file mode 100644 index 00000000000..ae1be83532c --- /dev/null +++ b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/notInitUsage.optimized.js @@ -0,0 +1,58 @@ +function getArg(x) { + return x; +} + +function test1(ok) { + var $tmp + getArg($tmp) + + $tmp = getArg(ok).toUpperCase(); + return $tmp +} + +function test2(ok) { + var $tmp + if ($tmp === undefined) { + $tmp = getArg(ok).toUpperCase(); + } + + return $tmp +} + +function test3(ok) { + var $tmp + if (ok) { + if (ok) { + getArg($tmp) + $tmp = getArg(ok).toUpperCase(); + } + } + return $tmp +} + +function test4(ok) { + var $tmp + if (ok) { + if (ok) { + getArg($tmp) + } + $tmp = getArg(ok).toUpperCase(); + } + return $tmp +} + +function box() { + if (test1("ok") != "OK") { + return "Fail test1" + } + if (test2("ok") != "OK") { + return "Fail test2" + } + if (test3("ok") != "OK") { + return "Fail test3" + } + if (test4("ok") != "OK") { + return "Fail test4" + } + return "OK" +} diff --git a/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/notInitUsage.original.js b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/notInitUsage.original.js new file mode 100644 index 00000000000..ae1be83532c --- /dev/null +++ b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/notInitUsage.original.js @@ -0,0 +1,58 @@ +function getArg(x) { + return x; +} + +function test1(ok) { + var $tmp + getArg($tmp) + + $tmp = getArg(ok).toUpperCase(); + return $tmp +} + +function test2(ok) { + var $tmp + if ($tmp === undefined) { + $tmp = getArg(ok).toUpperCase(); + } + + return $tmp +} + +function test3(ok) { + var $tmp + if (ok) { + if (ok) { + getArg($tmp) + $tmp = getArg(ok).toUpperCase(); + } + } + return $tmp +} + +function test4(ok) { + var $tmp + if (ok) { + if (ok) { + getArg($tmp) + } + $tmp = getArg(ok).toUpperCase(); + } + return $tmp +} + +function box() { + if (test1("ok") != "OK") { + return "Fail test1" + } + if (test2("ok") != "OK") { + return "Fail test2" + } + if (test3("ok") != "OK") { + return "Fail test3" + } + if (test4("ok") != "OK") { + return "Fail test4" + } + return "OK" +} diff --git a/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/sameBlock.optimized.js b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/sameBlock.optimized.js new file mode 100644 index 00000000000..10f6ca5dbc9 --- /dev/null +++ b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/sameBlock.optimized.js @@ -0,0 +1,16 @@ +function getArg(x) { + return x; +} + +function test(o, k) { + var $tmp1 = getArg(k).toUpperCase(); + var $tmp2 = getArg(o).toUpperCase(); + + var O = getArg($tmp2); + var K = getArg($tmp1); + return O + K; +} + +function box() { + return test("o", "k"); +} diff --git a/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/sameBlock.original.js b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/sameBlock.original.js new file mode 100644 index 00000000000..d93f4ac5194 --- /dev/null +++ b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/sameBlock.original.js @@ -0,0 +1,18 @@ +function getArg(x) { + return x; +} + +function test(o, k) { + var $tmp1; + var $tmp2; + $tmp1 = getArg(k).toUpperCase(); + $tmp2 = getArg(o).toUpperCase(); + + var O = getArg($tmp2); + var K = getArg($tmp1); + return O + K; +} + +function box() { + return test("o", "k"); +} diff --git a/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/siblingBlocks.optimized.js b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/siblingBlocks.optimized.js new file mode 100644 index 00000000000..d0efaa9ffdc --- /dev/null +++ b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/siblingBlocks.optimized.js @@ -0,0 +1,109 @@ +function getArg(x) { + return x; +} + +function test1(o, k) { + var $tmp1; + var $tmp2; + if (o != k) { + $tmp1 = getArg(k).toUpperCase(); + $tmp2 = getArg(o).toUpperCase(); + } else { + $tmp1 = getArg(k) + $tmp2 = getArg(o) + } + + var O = getArg($tmp2); + var K = getArg($tmp1); + return O + K; +} + +function test2(o, k) { + var $tmp1; + var $tmp2; + if (o != k) { + if (o) { + $tmp1 = getArg(k).toUpperCase(); + $tmp2 = getArg(o).toUpperCase(); + } else { + $tmp1 = getArg(k) + $tmp2 = getArg(o) + } + var O = getArg($tmp2); + var K = getArg($tmp1); + } + + return O + K; +} + +function test3(o, k) { + if (o != k) { + var $tmp1 = getArg(k).toUpperCase(); + var $tmp2 = getArg(k).toUpperCase(); + if (o) { + $tmp1 = getArg(k).toUpperCase(); + $tmp2 = getArg(o).toUpperCase(); + } else { + $tmp1 = getArg(k) + $tmp2 = getArg(o) + } + var O = getArg($tmp2); + var K = getArg($tmp1); + } + + return O + K; +} + +function test4(ok) { + if (ok) { + var $tmp1 = getArg(ok).toUpperCase(); + if (ok) { + if (ok) { + getArg($tmp1) + } + } else { + getArg($tmp1) + } + var OK = getArg($tmp1); + } + + return OK; +} + +function test5(ok) { + if (ok) { + var $tmp1 = 0 + if (ok) { + $tmp1 = 1 + if (ok) { + getArg($tmp1) + } else { + $tmp1 = 2 + } + $tmp1 = getArg(ok).toUpperCase(); + } else { + getArg($tmp1) + } + var OK = getArg($tmp1); + } + return OK; +} + +function box() { + if (test1("o", "k") != "OK") { + return "Fail test1" + } + if (test2("o", "k") != "OK") { + return "Fail test2" + } + if (test3("o", "k") != "OK") { + return "Fail test3" + } + if (test4("OK") != "OK") { + return "Fail test4" + } + if (test5("OK") != "OK") { + return "Fail test5" + } + return "OK" +} diff --git a/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/siblingBlocks.original.js b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/siblingBlocks.original.js new file mode 100644 index 00000000000..54d90cdf5f3 --- /dev/null +++ b/js/js.translator/testData/js-optimizer/move-temporary-variable-declaration/siblingBlocks.original.js @@ -0,0 +1,114 @@ +function getArg(x) { + return x; +} + +function test1(o, k) { + var $tmp1; + var $tmp2; + if (o != k) { + $tmp1 = getArg(k).toUpperCase(); + $tmp2 = getArg(o).toUpperCase(); + } else { + $tmp1 = getArg(k) + $tmp2 = getArg(o) + } + + var O = getArg($tmp2); + var K = getArg($tmp1); + return O + K; +} + +function test2(o, k) { + var $tmp1; + var $tmp2; + if (o != k) { + if (o) { + $tmp1 = getArg(k).toUpperCase(); + $tmp2 = getArg(o).toUpperCase(); + } else { + $tmp1 = getArg(k) + $tmp2 = getArg(o) + } + var O = getArg($tmp2); + var K = getArg($tmp1); + } + + return O + K; +} + +function test3(o, k) { + var $tmp1; + var $tmp2; + if (o != k) { + $tmp1 = getArg(k).toUpperCase(); + $tmp2 = getArg(k).toUpperCase(); + if (o) { + $tmp1 = getArg(k).toUpperCase(); + $tmp2 = getArg(o).toUpperCase(); + } else { + $tmp1 = getArg(k) + $tmp2 = getArg(o) + } + var O = getArg($tmp2); + var K = getArg($tmp1); + } + + return O + K; +} + +function test4(ok) { + var $tmp1; + if (ok) { + $tmp1 = getArg(ok).toUpperCase(); + if (ok) { + if (ok) { + getArg($tmp1) + } + } else { + getArg($tmp1) + } + var OK = getArg($tmp1); + } + + return OK; +} + +function test5(ok) { + var $tmp1; + + if (ok) { + $tmp1 = 0 + if (ok) { + $tmp1 = 1 + if (ok) { + getArg($tmp1) + } else { + $tmp1 = 2 + } + $tmp1 = getArg(ok).toUpperCase(); + } else { + getArg($tmp1) + } + var OK = getArg($tmp1); + } + return OK; +} + +function box() { + if (test1("o", "k") != "OK") { + return "Fail test1" + } + if (test2("o", "k") != "OK") { + return "Fail test2" + } + if (test3("o", "k") != "OK") { + return "Fail test3" + } + if (test4("OK") != "OK") { + return "Fail test4" + } + if (test5("OK") != "OK") { + return "Fail test5" + } + return "OK" +} diff --git a/js/js.translator/testData/js-optimizer/temporary-assignment/ifWithoutElse.optimized.js b/js/js.translator/testData/js-optimizer/temporary-assignment/ifWithoutElse.optimized.js index 9aa8a230856..725421084fa 100644 --- a/js/js.translator/testData/js-optimizer/temporary-assignment/ifWithoutElse.optimized.js +++ b/js/js.translator/testData/js-optimizer/temporary-assignment/ifWithoutElse.optimized.js @@ -3,18 +3,18 @@ function f(x) { } function box() { - var result1, result2, $a, $b; + var $a, $b; if (f(true)) { $a = "1"; } - result1 = $a; + var result1 = $a; if (result1 !== "1") return "fail1: " + result1; if (f(false)) { $b = "1"; } - result2 = $b; + var result2 = $b; if (result2 !== void 0) return "fail2: " + result2; return "OK"; diff --git a/js/js.translator/testData/js-optimizer/temporary-assignment/transitiveChain.optimized.js b/js/js.translator/testData/js-optimizer/temporary-assignment/transitiveChain.optimized.js index 351a917141e..7d7ca5196b4 100644 --- a/js/js.translator/testData/js-optimizer/temporary-assignment/transitiveChain.optimized.js +++ b/js/js.translator/testData/js-optimizer/temporary-assignment/transitiveChain.optimized.js @@ -13,32 +13,28 @@ function testIrregular() { } function testDoubleUse1() { - var $b; - $b = f("testDoubleUse1"); + var $b = f("testDoubleUse1"); var result = $b; f($b); return result; } function testDoubleUse2() { - var $d; - $d = f("testDoubleUse2"); + var $d = f("testDoubleUse2"); var result = $d; f($d); return result; } function testDoubleUse3() { - var $a; - $a = f("testDoubleUse3"); + var $a = f("testDoubleUse3"); var result = $a; f($a); return result; } function testCircular() { - var $b; - $b = f("testCircular"); + var $b = f("testCircular"); $b = $b; var result = $b; return result; diff --git a/js/js.translator/testData/js-optimizer/temporary-variable/assignmentToNonLocal.optimized.js b/js/js.translator/testData/js-optimizer/temporary-variable/assignmentToNonLocal.optimized.js index 6129dc7799a..2be4b470554 100644 --- a/js/js.translator/testData/js-optimizer/temporary-variable/assignmentToNonLocal.optimized.js +++ b/js/js.translator/testData/js-optimizer/temporary-variable/assignmentToNonLocal.optimized.js @@ -1,9 +1,8 @@ var result = ""; function box() { - var $tmp; - $tmp = result; + var $tmp = result; result += "fail"; result = $tmp + "OK"; return result; -} \ No newline at end of file +} diff --git a/js/js.translator/testData/lineNumbers/coroutine.kt b/js/js.translator/testData/lineNumbers/coroutine.kt index 1b3f91b71fa..334744c9898 100644 --- a/js/js.translator/testData/lineNumbers/coroutine.kt +++ b/js/js.translator/testData/lineNumbers/coroutine.kt @@ -15,5 +15,5 @@ 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 * 93 93 3 45 45 7 7 6 9 9 * 9 * 9 * 10 10 * 11 * 11 12 12 * 13 * 13 14 14 15 15 +// LINES(JS_IR): 4 4 * 93 3 45 45 7 7 6 9 9 * 9 * 9 * 10 10 * 11 * 11 12 12 * 13 * 13 14 14 15 15