From c5c98798747ab9b0a86bafb41c483cebe0830520 Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Thu, 16 Jun 2022 16:06:19 +0200 Subject: [PATCH 1/2] feat: add an option to translate JS_IR with DCE. --- .../server/compiler/components/KotlinToJSTranslator.kt | 9 ++++++--- .../server/controllers/CompilerRestController.kt | 5 +++-- .../server/controllers/KotlinPlaygroundRestController.kt | 5 +++-- .../com/compiler/server/service/KotlinProjectExecutor.kt | 8 +++++--- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/compiler/server/compiler/components/KotlinToJSTranslator.kt b/src/main/kotlin/com/compiler/server/compiler/components/KotlinToJSTranslator.kt index d721cc259..5a491242f 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/KotlinToJSTranslator.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/KotlinToJSTranslator.kt @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.ir.backend.js.CompilerResult import org.jetbrains.kotlin.ir.backend.js.compile import org.jetbrains.kotlin.ir.backend.js.prepareAnalyzedSourceModule import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.js.config.JsConfig import org.jetbrains.kotlin.js.facade.K2JSTranslator @@ -100,7 +101,8 @@ class KotlinToJSTranslator( fun doTranslateWithIr( files: List, arguments: List, - coreEnvironment: KotlinCoreEnvironment + coreEnvironment: KotlinCoreEnvironment, + shouldEliminateDeadCode: Boolean ): TranslationJSResult { val currentProject = coreEnvironment.project @@ -121,13 +123,14 @@ class KotlinToJSTranslator( ir.context, arguments, fullJs = true, - dceJs = false, + dceJs = shouldEliminateDeadCode, multiModule = false, relativeRequirePath = true, ) val compiledModule: CompilerResult = transformer.generateModule(ir.allModules) - val jsCode = compiledModule.outputs.values.single().jsCode + val mode = if (shouldEliminateDeadCode) TranslationMode.FULL_DCE else TranslationMode.FULL + val jsCode = compiledModule.outputs[mode]!!.jsCode val listLines = jsCode .lineSequence() diff --git a/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt b/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt index 1ad936363..8f5ac27a3 100644 --- a/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt +++ b/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt @@ -19,9 +19,10 @@ class CompilerRestController(private val kotlinProjectExecutor: KotlinProjectExe @PostMapping("/translate") fun translateKotlinProjectEndpoint( @RequestBody project: Project, - @RequestParam(defaultValue = "false") ir: Boolean + @RequestParam(defaultValue = "false") ir: Boolean, + @RequestParam(defaultValue = "false") dce: Boolean ): TranslationJSResult { - return if (ir) kotlinProjectExecutor.convertToJsIr(project) + return if (ir) kotlinProjectExecutor.convertToJsIr(project, dce) else kotlinProjectExecutor.convertToJs(project) } diff --git a/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt b/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt index 2d12cd2b3..ce126f8da 100644 --- a/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt +++ b/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt @@ -29,7 +29,8 @@ class KotlinPlaygroundRestController(private val kotlinProjectExecutor: KotlinPr @RequestParam type: String, @RequestParam(required = false) line: Int?, @RequestParam(required = false) ch: Int?, - @RequestParam(required = false) project: Project? + @RequestParam(required = false) project: Project?, + @RequestParam(defaultValue = "false") dce: Boolean ): ResponseEntity<*> { val result = when (type) { "getKotlinVersions" -> listOf(kotlinProjectExecutor.getVersion()) @@ -40,7 +41,7 @@ class KotlinPlaygroundRestController(private val kotlinProjectExecutor: KotlinPr when (project.confType) { ProjectType.JAVA -> kotlinProjectExecutor.run(project) ProjectType.JS, ProjectType.CANVAS -> kotlinProjectExecutor.convertToJs(project) - ProjectType.JS_IR -> kotlinProjectExecutor.convertToJsIr(project) + ProjectType.JS_IR -> kotlinProjectExecutor.convertToJsIr(project, dce) ProjectType.JUNIT -> kotlinProjectExecutor.test(project) } } diff --git a/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt b/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt index 22ab3c5bd..d2e3c6c2a 100644 --- a/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt +++ b/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt @@ -51,8 +51,10 @@ class KotlinProjectExecutor( return convertJsWithConverter(project, kotlinToJSTranslator::doTranslate) } - fun convertToJsIr(project: Project): TranslationJSResult { - return convertJsWithConverter(project, kotlinToJSTranslator::doTranslateWithIr) + fun convertToJsIr(project: Project, shouldEliminateDeadCode: Boolean): TranslationJSResult { + return convertJsWithConverter(project) { files, args, env -> + kotlinToJSTranslator.doTranslateWithIr(files, args, env, shouldEliminateDeadCode) + } } fun complete(project: Project, line: Int, character: Int): List { @@ -89,7 +91,7 @@ class KotlinProjectExecutor( private fun convertJsWithConverter( project: Project, - converter: KFunction3, List, KotlinCoreEnvironment, TranslationJSResult> + converter: (List, List, KotlinCoreEnvironment) -> TranslationJSResult ): TranslationJSResult { return kotlinEnvironment.environment { environment -> val files = getFilesFrom(project, environment).map { it.kotlinFile } From 071c7c2b989e5c9c83b1513fa876c7a47ce4d156 Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Thu, 28 Jul 2022 18:49:25 +0200 Subject: [PATCH 2/2] test: cover DCE flag with a few test cases. --- .../server/CommandLineArgumentsTest.kt | 20 ++++++ .../compiler/server/ConcurrencyRunnerTest.kt | 13 +++- .../compiler/server/ConvertToJsRunnerTest.kt | 21 ++++++ .../compiler/server/base/BaseExecutorTest.kt | 23 +++++- .../server/generator/TestProjectRunner.kt | 70 ++++++++++++++++--- 5 files changed, 134 insertions(+), 13 deletions(-) diff --git a/src/test/kotlin/com/compiler/server/CommandLineArgumentsTest.kt b/src/test/kotlin/com/compiler/server/CommandLineArgumentsTest.kt index c4447ce92..a68a8cb97 100644 --- a/src/test/kotlin/com/compiler/server/CommandLineArgumentsTest.kt +++ b/src/test/kotlin/com/compiler/server/CommandLineArgumentsTest.kt @@ -41,6 +41,16 @@ class CommandLineArgumentsTest : BaseExecutorTest() { ) } + @Test + fun `command line arguments js ir test with dce`() { + runJsIrWithDce( + code = "val unusedVariable = 42\nfun main(args: Array) {\n println(args[0])\n println(args[1])\n}", + args = "0 1", + contains = "main(['0', '1']);", + notContain = "var unusedVariable" + ) + } + @Test fun `command line string arguments js test`() { runJs( @@ -58,4 +68,14 @@ class CommandLineArgumentsTest : BaseExecutorTest() { contains = "main(['alex1', 'alex2']);" ) } + + @Test + fun `command line string arguments js ir test with dce`() { + runJsIrWithDce( + code = "class Garfield\nfun main(args: Array) {\n println(args[0])\n println(args[1])\n}", + args = "alex1 alex2", + contains = "main(['alex1', 'alex2']);", + notContain = "function Garfield" + ) + } } \ No newline at end of file diff --git a/src/test/kotlin/com/compiler/server/ConcurrencyRunnerTest.kt b/src/test/kotlin/com/compiler/server/ConcurrencyRunnerTest.kt index aad61afc7..75ce4282b 100644 --- a/src/test/kotlin/com/compiler/server/ConcurrencyRunnerTest.kt +++ b/src/test/kotlin/com/compiler/server/ConcurrencyRunnerTest.kt @@ -49,7 +49,7 @@ class ConcurrencyRunnerTest : BaseExecutorTest() { @Test fun `a lot of hello word test JS`() { runManyTest { - runJsIr( + runJs( code = "fun main() {\n println(\"Hello, world!!!\")\n}", contains = "println('Hello, world!!!');" ) @@ -66,6 +66,17 @@ class ConcurrencyRunnerTest : BaseExecutorTest() { } } + @Test + fun `a lot of hello word test JS IR with DCE`() { + runManyTest { + runJsIrWithDce( + code = "enum class UnusedEnum {A, B}\nfun main() {\n println(\"Hello, world!!!\")\n}", + contains = "println('Hello, world!!!');", + notContain = "function UnusedEnum" + ) + } + } + private fun runManyTest(times: Int = 100, test: () -> Unit) { runBlocking { launch(Dispatchers.IO) { diff --git a/src/test/kotlin/com/compiler/server/ConvertToJsRunnerTest.kt b/src/test/kotlin/com/compiler/server/ConvertToJsRunnerTest.kt index fbf9b25ae..500aefe79 100644 --- a/src/test/kotlin/com/compiler/server/ConvertToJsRunnerTest.kt +++ b/src/test/kotlin/com/compiler/server/ConvertToJsRunnerTest.kt @@ -42,6 +42,15 @@ class ConvertToJsIrRunnerTest : BaseExecutorTest() { ) } + @Test + fun `base execute test with DCE`() { + runJsIrWithDce( + code = "class Cat\nfun main() {\n println(\"Hello, world!!!\")\n}", + contains = "println('Hello, world!!!');", + notContain = "function Cat" + ) + } + @Test fun `base execute test multi`() { runJsIr( @@ -52,4 +61,16 @@ class ConvertToJsIrRunnerTest : BaseExecutorTest() { contains = "var cat = new Cat('Kitty');" ) } + + @Test + fun `base execute test multi with DCE`() { + runJsIrWithDce( + code = listOf( + "import cat.Cat\n\nfun lonelyFun() {}\n\nfun main(args: Array) {\nval cat = Cat(\"Kitty\")\nprintln(cat.name)\n}", + "package cat\n class Cat(val name: String)" + ), + contains = "var cat = new Cat('Kitty');", + notContain = "function lonelyFun" + ) + } } \ No newline at end of file diff --git a/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt b/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt index a38d26c21..7fa791567 100644 --- a/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt +++ b/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt @@ -50,16 +50,33 @@ class BaseExecutorTest { fun runJsIr( code: String, contains: String, - args: String = "" + args: String = "", ) = testRunner.runJs(code, contains, args) { project -> - convertToJsIr(project) + convertToJsIr(project, shouldEliminateDeadCode = false) } fun runJsIr( code: List, contains: String ) = testRunner.multiRunJs(code, contains) { project -> - convertToJsIr(project) + convertToJsIr(project, shouldEliminateDeadCode = false) + } + + fun runJsIrWithDce( + code: String, + contains: String = "", + notContain: String = "", + args: String = "", + ) = testRunner.runJsDce(code, contains, notContain, args) { project -> + convertToJsIr(project, shouldEliminateDeadCode = true) + } + + fun runJsIrWithDce( + code: List, + contains: String = "", + notContain: String = "", + ) = testRunner.multiRunJsDce(code, contains, notContain) { project -> + convertToJsIr(project, shouldEliminateDeadCode = true) } fun translateToJs(code: String) = testRunner.translateToJs(code) diff --git a/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt b/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt index 9a40972be..eee60da69 100644 --- a/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt +++ b/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt @@ -25,6 +25,37 @@ class TestProjectRunner { runAndTest(project, contains) } + fun runJsDce( + code: String, + contains: String, + notContain: String, + args: String = "", + convert: KotlinProjectExecutor.(Project) -> TranslationJSResult + ) { + val project = generateSingleProject(text = code, args = args, projectType = ProjectType.JS) + convertAndTest(project, convert) { + isNotNull() + hasNoErrors() + contains(contains) + shouldNotContain(notContain) + } + } + + fun multiRunJsDce( + code: List, + contains: String, + notContain: String, + convert: KotlinProjectExecutor.(Project) -> TranslationJSResult + ) { + val project = generateMultiProject(*code.toTypedArray(), projectType = ProjectType.JS) + convertAndTest(project, convert) { + isNotNull() + hasNoErrors() + contains(contains) + shouldNotContain(notContain) + } + } + fun runJs( code: String, contains: String, @@ -32,7 +63,11 @@ class TestProjectRunner { convert: KotlinProjectExecutor.(Project) -> TranslationJSResult ) { val project = generateSingleProject(text = code, args = args, projectType = ProjectType.JS) - convertAndTest(project, contains, convert) + convertAndTest(project, convert) { + isNotNull() + hasNoErrors() + contains(contains) + } } fun multiRunJs( @@ -41,7 +76,11 @@ class TestProjectRunner { convert: KotlinProjectExecutor.(Project) -> TranslationJSResult ) { val project = generateMultiProject(*code.toTypedArray(), projectType = ProjectType.JS) - convertAndTest(project, contains, convert) + convertAndTest(project, convert) { + isNotNull() + hasNoErrors() + contains(contains) + } } fun translateToJs(code: String): TranslationJSResult { @@ -127,14 +166,27 @@ class TestProjectRunner { private fun convertAndTest( project: Project, - contains: String, - convert: KotlinProjectExecutor.(Project) -> TranslationJSResult + convert: KotlinProjectExecutor.(Project) -> TranslationJSResult, + test: TranslationJSResult.() -> Unit ) { - val result = kotlinProjectExecutor.convert(project) - Assertions.assertNotNull(result, "Test result should no be a null") - Assertions.assertFalse(result.hasErrors) { - "Test contains errors!\n\n" + renderErrorDescriptors(result.errors.filterOnlyErrors) + kotlinProjectExecutor.convert(project).test() + } + + private fun TranslationJSResult.isNotNull() { + Assertions.assertNotNull(this, "Test result should no be a null") + } + + private fun TranslationJSResult.hasNoErrors() { + Assertions.assertFalse(hasErrors) { + "Test contains errors!\n\n" + renderErrorDescriptors(errors.filterOnlyErrors) } - Assertions.assertTrue(result.jsCode!!.contains(contains), "Actual: ${result.jsCode}. \n Expected: $contains") + } + + private fun TranslationJSResult.contains(substring: String) { + Assertions.assertTrue(jsCode!!.contains(substring), "Actual: ${jsCode}. \n Expected: $substring") + } + + private fun TranslationJSResult.shouldNotContain(substring: String) { + Assertions.assertFalse(jsCode!!.contains(substring), "Actual: ${jsCode}. \n Expected to eliminate: $substring") } } \ No newline at end of file