Skip to content

feat: add an option to translate JS_IR with DCE. #501

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -100,7 +101,8 @@ class KotlinToJSTranslator(
fun doTranslateWithIr(
files: List<KtFile>,
arguments: List<String>,
coreEnvironment: KotlinCoreEnvironment
coreEnvironment: KotlinCoreEnvironment,
shouldEliminateDeadCode: Boolean
): TranslationJSResult {
val currentProject = coreEnvironment.project

Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, fix the usages of the method or set a default value for "shouldEliminateDeadCode".
BaseExecutorTest will not pass due to the method's signature mismatch.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also it;s good to have additional test for that feature

return convertJsWithConverter(project) { files, args, env ->
kotlinToJSTranslator.doTranslateWithIr(files, args, env, shouldEliminateDeadCode)
}
}

fun complete(project: Project, line: Int, character: Int): List<Completion> {
Expand Down Expand Up @@ -89,7 +91,7 @@ class KotlinProjectExecutor(

private fun convertJsWithConverter(
project: Project,
converter: KFunction3<List<KtFile>, List<String>, KotlinCoreEnvironment, TranslationJSResult>
converter: (List<KtFile>, List<String>, KotlinCoreEnvironment) -> TranslationJSResult
): TranslationJSResult {
return kotlinEnvironment.environment { environment ->
val files = getFilesFrom(project, environment).map { it.kotlinFile }
Expand Down
20 changes: 20 additions & 0 deletions src/test/kotlin/com/compiler/server/CommandLineArgumentsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>) {\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(
Expand All @@ -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<String>) {\n println(args[0])\n println(args[1])\n}",
args = "alex1 alex2",
contains = "main(['alex1', 'alex2']);",
notContain = "function Garfield"
)
}
}
13 changes: 12 additions & 1 deletion src/test/kotlin/com/compiler/server/ConcurrencyRunnerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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!!!');"
)
Expand All @@ -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) {
Expand Down
21 changes: 21 additions & 0 deletions src/test/kotlin/com/compiler/server/ConvertToJsRunnerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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<String>) {\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"
)
}
}
23 changes: 20 additions & 3 deletions src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
contains: String
) = testRunner.multiRunJs(code, contains) { project ->
convertToJsIr(project)
convertToJsIr(project, shouldEliminateDeadCode = false)
}

fun runJsIrWithDce(
code: String,
contains: String = "",
notContain: String = "",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

notContains mb?

args: String = "",
) = testRunner.runJsDce(code, contains, notContain, args) { project ->
convertToJsIr(project, shouldEliminateDeadCode = true)
}

fun runJsIrWithDce(
code: List<String>,
contains: String = "",
notContain: String = "",
) = testRunner.multiRunJsDce(code, contains, notContain) { project ->
convertToJsIr(project, shouldEliminateDeadCode = true)
}

fun translateToJs(code: String) = testRunner.translateToJs(code)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,49 @@ 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<String>,
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,
args: String = "",
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(
Expand All @@ -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 {
Expand Down Expand Up @@ -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")
}
}