From 4d59a582914b20831e54c3cc6b388a40d3f4d0c5 Mon Sep 17 00:00:00 2001 From: Svyatoslav Kuzmich Date: Tue, 20 Oct 2020 11:36:43 +0300 Subject: [PATCH] [Wasm] Add interop annotation @JsFun --- .../jetbrains/kotlin/backend/wasm/compiler.kt | 13 ++++------- .../backend/wasm/ir2wasm/BodyGenerator.kt | 2 +- .../wasm/ir2wasm/DeclarationGenerator.kt | 14 +++++++++++- .../ir2wasm/WasmCompiledModuleFragment.kt | 5 ++++- .../wasm/ir2wasm/WasmModuleCodegenContext.kt | 1 + .../ir2wasm/WasmModuleCodegenContextImpl.kt | 5 +++++ .../kotlin/backend/wasm/utils/Annotations.kt | 3 +++ .../kotlin/js/test/BasicWasmBoxTest.kt | 4 ++-- .../stdlib/wasm/builtins/kotlin/String.kt | 4 ++-- .../internal/kotlin/wasm/internal/JsArray.kt | 4 ++-- libraries/stdlib/wasm/src/kotlin/JsInterop.kt | 22 +++++++++++++++++++ 11 files changed, 59 insertions(+), 18 deletions(-) create mode 100644 libraries/stdlib/wasm/src/kotlin/JsInterop.kt diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt index 81ecd222755..a0ab3d1db13 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt @@ -99,14 +99,6 @@ fun compileWasm( fun WasmCompiledModuleFragment.generateJs(): String { val runtime = """ const runtime = { - String_plus(str1, str2) { - return str1 + String(str2); - }, - - String_getLength(str) { - return str.length; - }, - String_getChar(str, index) { return str.charCodeAt(index); }, @@ -163,5 +155,8 @@ fun WasmCompiledModuleFragment.generateJs(): String { }; """.trimIndent() - return runtime + generateStringLiteralsSupport(stringLiterals) + val jsCode = + "\nconst js_code = {${jsFuns.joinToString(",\n") { "\"" + it.importName + "\" : " + it.jsCode }}};" + + return runtime + generateStringLiteralsSupport(stringLiterals) + jsCode } diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt index 2dae119441c..8e4e8701f5a 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt @@ -208,7 +208,7 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV // Return types of imported functions cannot have concrete struct/array references. // Non-primitive return types are represented as eqref which need to be casted back to expected type on call site. - if (function.getWasmImportAnnotation() != null) { + if (function.getWasmImportAnnotation() != null || function.getJsFunAnnotation() != null) { val resT = context.transformResultType(function.returnType) if (resT is WasmRefNullType) { generateTypeRTT(function.returnType) diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt index 467e654be56..0535998afd0 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.wasm.ir2wasm import org.jetbrains.kotlin.backend.common.ir.isOverridableOrOverrides import org.jetbrains.kotlin.backend.wasm.WasmBackendContext +import org.jetbrains.kotlin.backend.wasm.lower.wasmSignature import org.jetbrains.kotlin.backend.wasm.utils.* import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.Modality @@ -35,12 +36,23 @@ class DeclarationGenerator(val context: WasmModuleCodegenContext) : IrElementVis // Type aliases are not material } + private fun jsCodeName(declaration: IrFunction): String { + return declaration.fqNameWhenAvailable!!.asString() + "_" + (declaration as IrSimpleFunction).wasmSignature(irBuiltIns).hashCode() + } + override fun visitFunction(declaration: IrFunction) { // Inline class constructors are currently empty if (declaration is IrConstructor && backendContext.inlineClassesUtils.isClassInlineLike(declaration.parentAsClass)) return - val importedName = declaration.getWasmImportAnnotation() + val jsCode = declaration.getJsFunAnnotation() + val importedName = if (jsCode != null) { + val jsCodeName = jsCodeName(declaration) + context.addJsFun(jsCodeName, jsCode) + WasmImportPair("js_code", jsCodeName(declaration)) + } else { + declaration.getWasmImportAnnotation() + } val isIntrinsic = declaration.hasWasmReinterpretAnnotation() || declaration.getWasmOpAnnotation() != null if (isIntrinsic) { diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt index 68192a71dd6..bb8e8842606 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt @@ -46,7 +46,10 @@ class WasmCompiledModuleFragment { ReferencableAndDefinable() val exports = mutableListOf>() - // + class JsCodeSnippet(val importName: String, val jsCode: String) + + val jsFuns = mutableListOf() + var startFunction: WasmFunction? = null open class ReferencableElements { diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt index df84575374b..b0d380faa42 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt @@ -22,6 +22,7 @@ interface WasmModuleCodegenContext : WasmBaseCodegenContext { fun defineStructType(irClass: IrClassSymbol, wasmStruct: WasmStructDeclaration) fun defineRTT(irClass: IrClassSymbol, wasmGlobal: WasmGlobal) fun defineFunctionType(irFunction: IrFunctionSymbol, wasmFunctionType: WasmFunctionType) + fun addJsFun(importName: String, jsCode: String) fun setStartFunction(wasmFunction: WasmFunction) fun addExport(wasmExport: WasmExport<*>) diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt index c642012504e..7759bd5834f 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt @@ -166,4 +166,9 @@ class WasmModuleCodegenContextImpl( val fieldId = metadata.fields.indexOf(field) return WasmSymbol(fieldId) } + + override fun addJsFun(importName: String, jsCode: String) { + wasmFragment.jsFuns += + WasmCompiledModuleFragment.JsCodeSnippet(importName = importName, jsCode = jsCode) + } } \ No newline at end of file diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/utils/Annotations.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/utils/Annotations.kt index f61297926fe..f303b6707cf 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/utils/Annotations.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/utils/Annotations.kt @@ -35,3 +35,6 @@ fun IrAnnotationContainer.getWasmImportAnnotation(): WasmImportPair? = (it.getValueArgument(1) as IrConst<*>).value as String ) } + +fun IrAnnotationContainer.getJsFunAnnotation(): String? = + getAnnotation(FqName("kotlin.JsFun"))?.getSingleConstStringArgument() diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt index a23c8828dcf..68594c305d3 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt @@ -115,7 +115,7 @@ abstract class BasicWasmBoxTest( testFunction: String ) { val filesToCompile = units.map { (it as TranslationUnit.SourceFile).file } - val debugMode = getBoolean("kotlin.js.debugMode") + val debugMode = getBoolean("kotlin.wasm.debugMode") val phaseConfig = if (debugMode) { val allPhasesSet = wasmPhases.toPhaseMap().values.toSet() @@ -154,7 +154,7 @@ abstract class BasicWasmBoxTest( val testRunner = """ const wasmBinary = read(String.raw`${outputWasmFile.absoluteFile}`, 'binary'); const wasmModule = new WebAssembly.Module(wasmBinary); - const wasmInstance = new WebAssembly.Instance(wasmModule, { runtime }); + const wasmInstance = new WebAssembly.Instance(wasmModule, { runtime, js_code }); const actualResult = wasmInstance.exports.$testFunction(); if (actualResult !== "OK") diff --git a/libraries/stdlib/wasm/builtins/kotlin/String.kt b/libraries/stdlib/wasm/builtins/kotlin/String.kt index 1871d498ad3..1ca1e42d318 100644 --- a/libraries/stdlib/wasm/builtins/kotlin/String.kt +++ b/libraries/stdlib/wasm/builtins/kotlin/String.kt @@ -51,11 +51,11 @@ public class String constructor(public val string: String) : Comparable, public override fun hashCode(): Int = 10 } -@WasmImport("runtime", "String_plus") +@JsFun("(it, other) => it + String(other)") private fun stringPlusImpl(it: String, other: String): String = implementedAsIntrinsic -@WasmImport("runtime", "String_getLength") +@JsFun("(it) => it.length") private fun stringLengthImpl(it: String): Int = implementedAsIntrinsic diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/JsArray.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/JsArray.kt index 58e6f2f004b..2d74af9b300 100644 --- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/JsArray.kt +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/JsArray.kt @@ -72,11 +72,11 @@ internal fun JsArray_set_WasmExternRef(array: WasmExternRef, index: Int, value: internal fun JsArray_getSize(array: WasmExternRef): Int = implementedAsIntrinsic -@WasmImport("runtime", "identity") +@JsFun("(x) => x") internal fun Any?.toWasmExternRef(): WasmExternRef = implementedAsIntrinsic -@WasmImport("runtime", "identity") +@JsFun("(x) => x") internal fun WasmExternRefToAny(ref: WasmExternRef): Any? = implementedAsIntrinsic diff --git a/libraries/stdlib/wasm/src/kotlin/JsInterop.kt b/libraries/stdlib/wasm/src/kotlin/JsInterop.kt new file mode 100644 index 00000000000..c2660e20681 --- /dev/null +++ b/libraries/stdlib/wasm/src/kotlin/JsInterop.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2020 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 kotlin + +/** + * Implements annotated function in JavaScript and automatically imports is to Wasm. + * [code] string must contain JS expression that evaluates to JS function with signature that matches annotated kotlin function + * + * For example, a function that adds two Doubles via JS: + * + * @JsFun("(x, y) => x + y") + * fun jsAdd(x: Double, y: Double): Double = + * error("...") + * + * This is a temporary annotation because K/Wasm <-> JS interop is not designed yet. + */ +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) +@Retention(AnnotationRetention.BINARY) +public annotation class JsFun(val code: String) \ No newline at end of file