From 2d87c39775f0c66e7a025f63cff29006089980f8 Mon Sep 17 00:00:00 2001 From: Ilya Goncharov Date: Fri, 10 Feb 2023 19:02:52 +0100 Subject: [PATCH] Wasm init --- build.gradle.kts | 22 +++++++ .../kotlin/component/KotlinEnvironment.kt | 11 +++- .../kotlin/KotlinEnvironmentConfiguration.kt | 3 +- .../compiler/components/KotlinEnvironment.kt | 2 +- .../components/KotlinToJSTranslator.kt | 58 ++++++++++++++++--- .../configuration/ApplicationConfiguration.kt | 4 +- .../controllers/CompilerRestController.kt | 19 +++--- .../compiler/server/model/ExecutionResult.kt | 17 +++++- .../model/KotlinTranslatableCompiler.kt | 6 ++ .../server/model/bean/LibrariesFile.kt | 6 +- .../server/service/KotlinProjectExecutor.kt | 17 +++--- 11 files changed, 133 insertions(+), 32 deletions(-) create mode 100644 src/main/kotlin/com/compiler/server/model/KotlinTranslatableCompiler.kt diff --git a/build.gradle.kts b/build.gradle.kts index 8544ced36..c4814de32 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,7 +30,19 @@ val kotlinJsDependency: Configuration by configurations.creating { ) } } + +val kotlinWasmDependency: Configuration by configurations.creating { + isTransitive = false + attributes { + attribute( + KotlinPlatformType.attribute, + KotlinPlatformType.wasm + ) + } +} + val libJSFolder = "$kotlinVersion-js" +val libWasmFolder = "$kotlinVersion-wasm" val libJVMFolder = kotlinVersion val propertyFile = "application.properties" val jacksonVersionKotlinDependencyJar = "2.14.0" // don't forget to update version in `executor.policy` file. @@ -44,6 +56,11 @@ val copyJSDependencies by tasks.creating(Copy::class) { into(libJSFolder) } +val copyWasmDependencies by tasks.creating(Copy::class) { + from(files(Callable { kotlinWasmDependency.map { zipTree(it) } })) + into(libWasmFolder) +} + plugins { id("org.springframework.boot") version "2.7.10" id("io.spring.dependency-management") version "1.1.0" @@ -89,6 +106,7 @@ dependencies { kotlinDependency("org.jetbrains.kotlin:kotlin-test:$kotlinVersion") kotlinDependency("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1") kotlinJsDependency("org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion") + kotlinWasmDependency("org.jetbrains.kotlin:kotlin-stdlib-wasm:$kotlinVersion") annotationProcessor("org.springframework:spring-context-indexer") implementation("com.google.code.gson:gson") @@ -134,7 +152,10 @@ fun generateProperties(prefix: String = "") = """ indexesJs.file=${prefix + indexesJs} libraries.folder.jvm=${prefix + libJVMFolder} libraries.folder.js=${prefix + libJSFolder} + libraries.folder.wasm=${prefix + libWasmFolder} spring.mvc.pathmatch.matching-strategy=ant_path_matcher + server.compression.enabled=true + server.compression.mime-types=application/json """.trimIndent() java { @@ -150,6 +171,7 @@ tasks.withType { } dependsOn(copyDependencies) dependsOn(copyJSDependencies) + dependsOn(copyWasmDependencies) dependsOn(":executors:jar") dependsOn(":indexation:run") buildPropertyFile() diff --git a/common/src/main/kotlin/component/KotlinEnvironment.kt b/common/src/main/kotlin/component/KotlinEnvironment.kt index 5dddf648f..301fc82fa 100644 --- a/common/src/main/kotlin/component/KotlinEnvironment.kt +++ b/common/src/main/kotlin/component/KotlinEnvironment.kt @@ -28,7 +28,8 @@ import java.io.File class KotlinEnvironment( val classpath: List, - additionalJsClasspath: List + additionalJsClasspath: List, + additionalWasmClasspath: List, ) { companion object { /** @@ -59,6 +60,7 @@ class KotlinEnvironment( } val JS_LIBRARIES = additionalJsClasspath.map { it.absolutePath } + val WASM_LIBRARIES = additionalWasmClasspath.map { it.absolutePath } @Synchronized fun environment(f: (KotlinCoreEnvironment) -> T): T { @@ -72,6 +74,13 @@ class KotlinEnvironment( put(JSConfigurationKeys.LIBRARIES, JS_LIBRARIES) } + val wasmConfiguration: CompilerConfiguration = configuration.copy().apply { + put(CommonConfigurationKeys.MODULE_NAME, "moduleId") + put(JSConfigurationKeys.LIBRARIES, WASM_LIBRARIES) + put(JSConfigurationKeys.WASM_ENABLE_ARRAY_RANGE_CHECKS, false) + put(JSConfigurationKeys.WASM_ENABLE_ASSERTS, false) + } + private val messageCollector = object : MessageCollector { override fun clear() {} override fun hasErrors(): Boolean { diff --git a/indexation/src/main/kotlin/KotlinEnvironmentConfiguration.kt b/indexation/src/main/kotlin/KotlinEnvironmentConfiguration.kt index cff2374ad..3ae0136dd 100644 --- a/indexation/src/main/kotlin/KotlinEnvironmentConfiguration.kt +++ b/indexation/src/main/kotlin/KotlinEnvironmentConfiguration.kt @@ -7,6 +7,7 @@ class KotlinEnvironmentConfiguration(fileName: String) { val kotlinEnvironment = run { val jvmFile = File(fileName) val jsFile = File("$fileName-js") + val wasmFile = File("$fileName-wasm") val classPath = listOfNotNull(jvmFile) .flatMap { @@ -15,6 +16,6 @@ class KotlinEnvironmentConfiguration(fileName: String) { } val additionalJsClasspath = listOfNotNull(jsFile) - KotlinEnvironment(classPath, additionalJsClasspath) + KotlinEnvironment(classPath, additionalJsClasspath, listOfNotNull(wasmFile)) } } diff --git a/src/main/kotlin/com/compiler/server/compiler/components/KotlinEnvironment.kt b/src/main/kotlin/com/compiler/server/compiler/components/KotlinEnvironment.kt index e450e73a9..96e11e57b 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/KotlinEnvironment.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/KotlinEnvironment.kt @@ -17,6 +17,6 @@ class KotlinEnvironmentConfiguration(val librariesFile: LibrariesFile) { } val additionalJsClasspath = listOfNotNull(librariesFile.js) - return KotlinEnvironment(classPath, additionalJsClasspath) + return KotlinEnvironment(classPath, additionalJsClasspath, listOfNotNull(librariesFile.wasm)) } } 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 14b4b9134..0cc1223c9 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/KotlinToJSTranslator.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/KotlinToJSTranslator.kt @@ -1,23 +1,25 @@ package com.compiler.server.compiler.components -import com.compiler.server.model.ErrorDescriptor -import com.compiler.server.model.TranslationJSResult -import com.compiler.server.model.toExceptionDescriptor +import com.compiler.server.model.* import component.KotlinEnvironment +import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig +import org.jetbrains.kotlin.backend.wasm.compileToLoweredIr +import org.jetbrains.kotlin.backend.wasm.compileWasm +import org.jetbrains.kotlin.backend.wasm.dce.eliminateDeadDeclarations +import org.jetbrains.kotlin.backend.wasm.wasmPhases import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment -import org.jetbrains.kotlin.ir.backend.js.CompilerResult -import org.jetbrains.kotlin.ir.backend.js.WholeWorldStageController -import org.jetbrains.kotlin.ir.backend.js.compile -import org.jetbrains.kotlin.ir.backend.js.prepareAnalyzedSourceModule +import org.jetbrains.kotlin.ir.backend.js.* 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.ir.declarations.impl.IrFactoryImplForJsIC import org.jetbrains.kotlin.js.config.JsConfig import org.jetbrains.kotlin.js.facade.K2JSTranslator import org.jetbrains.kotlin.js.facade.MainCallParameters import org.jetbrains.kotlin.js.facade.TranslationResult import org.jetbrains.kotlin.js.facade.exceptions.TranslationException +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.CompilerEnvironment import org.springframework.stereotype.Service @@ -47,8 +49,8 @@ class KotlinToJSTranslator( files: List, arguments: List, coreEnvironment: KotlinCoreEnvironment, - translate: (List, List, KotlinCoreEnvironment) -> TranslationJSResult - ): TranslationJSResult { + translate: (List, List, KotlinCoreEnvironment) -> TranslationResultWithJsCode + ): TranslationResultWithJsCode { val (errors, _) = errorAnalyzer.errorsFrom(files, coreEnvironment, isJs = true) return try { if (errorAnalyzer.isOnlyWarnings(errors)) { @@ -143,6 +145,44 @@ class KotlinToJSTranslator( return TranslationJSResult(listLines.joinToString("\n")) } + fun doTranslateWithWasm( + files: List, + arguments: List, + coreEnvironment: KotlinCoreEnvironment + ): TranslationWasmResult { + val currentProject = coreEnvironment.project + + val sourceModule = prepareAnalyzedSourceModule( + currentProject, + files, + kotlinEnvironment.wasmConfiguration, + kotlinEnvironment.WASM_LIBRARIES, + friendDependencies = emptyList(), + analyzer = AnalyzerWithCompilerReport(kotlinEnvironment.wasmConfiguration), + ) + + val (allModules, backendContext) = compileToLoweredIr( + depsDescriptors = sourceModule, + phaseConfig = PhaseConfig(wasmPhases), + irFactory = IrFactoryImpl, + exportedDeclarations = setOf(FqName("main")), + propertyLazyInitialization = true, + ) + eliminateDeadDeclarations(allModules, backendContext) + + val res = compileWasm( + allModules = allModules, + backendContext = backendContext, + baseFileName = "moduleId", + emitNameSection = false, + allowIncompleteImplementations = true, + generateWat = false, + generateSourceMaps = false + ) + + return TranslationWasmResult(res.jsUninstantiatedWrapper, res.wasm) + } + private fun getJsCodeFromModule(compiledModule: CompilerResult): String { val jsCodeObject = compiledModule.outputs.values.single() diff --git a/src/main/kotlin/com/compiler/server/configuration/ApplicationConfiguration.kt b/src/main/kotlin/com/compiler/server/configuration/ApplicationConfiguration.kt index d0b87ffce..cb81d062b 100644 --- a/src/main/kotlin/com/compiler/server/configuration/ApplicationConfiguration.kt +++ b/src/main/kotlin/com/compiler/server/configuration/ApplicationConfiguration.kt @@ -30,7 +30,8 @@ class ApplicationConfiguration( @Bean fun librariesFiles() = LibrariesFile( File(librariesFolderProperties.jvm), - File(librariesFolderProperties.js) + File(librariesFolderProperties.js), + File(librariesFolderProperties.wasm) ) } @@ -38,4 +39,5 @@ class ApplicationConfiguration( class LibrariesFolderProperties { lateinit var jvm: String lateinit var js: String + lateinit var wasm: String } \ No newline at end of file diff --git a/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt b/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt index 14b0795d1..a9b59d0f3 100644 --- a/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt +++ b/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt @@ -1,9 +1,6 @@ package com.compiler.server.controllers -import com.compiler.server.model.ErrorDescriptor -import com.compiler.server.model.ExecutionResult -import com.compiler.server.model.Project -import com.compiler.server.model.TranslationJSResult +import com.compiler.server.model.* import com.compiler.server.model.bean.VersionInfo import com.compiler.server.service.KotlinProjectExecutor import org.springframework.web.bind.annotation.* @@ -24,10 +21,16 @@ class CompilerRestController(private val kotlinProjectExecutor: KotlinProjectExe @PostMapping("/translate") fun translateKotlinProjectEndpoint( @RequestBody project: Project, - @RequestParam(defaultValue = "false") ir: Boolean - ): TranslationJSResult { - return if (ir) kotlinProjectExecutor.convertToJsIr(project) - else kotlinProjectExecutor.convertToJs(project) + @RequestParam(defaultValue = "false") ir: Boolean, + @RequestParam(defaultValue = "js") compiler: String + ): TranslationResultWithJsCode { + if (!ir) { + return kotlinProjectExecutor.convertToJs(project) + } + return when (KotlinTranslatableCompiler.valueOf(compiler.uppercase())) { + KotlinTranslatableCompiler.JS -> kotlinProjectExecutor.convertToJsIr(project) + KotlinTranslatableCompiler.WASM -> kotlinProjectExecutor.convertToWasm(project) + } } @PostMapping("/complete") diff --git a/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt b/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt index f459cd946..df05d1283 100644 --- a/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt +++ b/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt @@ -21,11 +21,24 @@ open class ExecutionResult( private fun textWithError() = text.startsWith(ERROR_STREAM_START) } +abstract class TranslationResultWithJsCode( + open val jsCode: String?, + errors: Map>, + exception: ExceptionDescriptor? +) : ExecutionResult(errors, exception) + data class TranslationJSResult( - val jsCode: String? = null, + override val jsCode: String? = null, override var exception: ExceptionDescriptor? = null, override var errors: Map> = emptyMap() -) : ExecutionResult(errors, exception) +) : TranslationResultWithJsCode(jsCode, errors, exception) + +data class TranslationWasmResult( + override val jsCode: String? = null, + val wasm: ByteArray, + override var exception: ExceptionDescriptor? = null, + override var errors: Map> = emptyMap() +) : TranslationResultWithJsCode(jsCode, errors, exception) @JsonInclude(JsonInclude.Include.NON_EMPTY) class JunitExecutionResult( diff --git a/src/main/kotlin/com/compiler/server/model/KotlinTranslatableCompiler.kt b/src/main/kotlin/com/compiler/server/model/KotlinTranslatableCompiler.kt new file mode 100644 index 000000000..77d7c8c6b --- /dev/null +++ b/src/main/kotlin/com/compiler/server/model/KotlinTranslatableCompiler.kt @@ -0,0 +1,6 @@ +package com.compiler.server.model + +enum class KotlinTranslatableCompiler { + JS, + WASM +} \ No newline at end of file diff --git a/src/main/kotlin/com/compiler/server/model/bean/LibrariesFile.kt b/src/main/kotlin/com/compiler/server/model/bean/LibrariesFile.kt index 9f7f77d51..87b79e9e1 100644 --- a/src/main/kotlin/com/compiler/server/model/bean/LibrariesFile.kt +++ b/src/main/kotlin/com/compiler/server/model/bean/LibrariesFile.kt @@ -2,4 +2,8 @@ package com.compiler.server.model.bean import java.io.File -class LibrariesFile(val jvm: File, val js: File) \ No newline at end of file +class LibrariesFile( + val jvm: File, + val js: File, + val wasm: File +) \ No newline at end of file diff --git a/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt b/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt index 6a958bc3f..c06d6280a 100644 --- a/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt +++ b/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt @@ -2,10 +2,7 @@ package com.compiler.server.service import com.compiler.server.compiler.KotlinFile import com.compiler.server.compiler.components.* -import com.compiler.server.model.ErrorDescriptor -import com.compiler.server.model.ExecutionResult -import com.compiler.server.model.Project -import com.compiler.server.model.TranslationJSResult +import com.compiler.server.model.* import com.compiler.server.model.bean.VersionInfo import component.KotlinEnvironment import model.Completion @@ -41,14 +38,18 @@ class KotlinProjectExecutor( }.also { logExecutionResult(project, it) } } - fun convertToJs(project: Project): TranslationJSResult { + fun convertToJs(project: Project): TranslationResultWithJsCode { return convertJsWithConverter(project, kotlinToJSTranslator::doTranslate) } - fun convertToJsIr(project: Project): TranslationJSResult { + fun convertToJsIr(project: Project): TranslationResultWithJsCode { return convertJsWithConverter(project, kotlinToJSTranslator::doTranslateWithIr) } + fun convertToWasm(project: Project): TranslationResultWithJsCode { + return convertJsWithConverter(project, kotlinToJSTranslator::doTranslateWithWasm) + } + fun complete(project: Project, line: Int, character: Int): List { return kotlinEnvironment.environment { val file = getFilesFrom(project, it).first() @@ -83,8 +84,8 @@ class KotlinProjectExecutor( private fun convertJsWithConverter( project: Project, - converter: (List, List, KotlinCoreEnvironment) -> TranslationJSResult - ): TranslationJSResult { + converter: (List, List, KotlinCoreEnvironment) -> TranslationResultWithJsCode + ): TranslationResultWithJsCode { return kotlinEnvironment.environment { environment -> val files = getFilesFrom(project, environment).map { it.kotlinFile } kotlinToJSTranslator.translate(