[Wasm] Fix js interop closure signature
Before we used IrType.render() which is not unique. This changes adds a proper serialization of adapter signature to string.
This commit is contained in:
committed by
Space Team
parent
e99b453816
commit
1b7c63bd49
+4
-4
@@ -58,10 +58,10 @@ class WasmBackendContext(
|
|||||||
|
|
||||||
override val mapping = JsMapping()
|
override val mapping = JsMapping()
|
||||||
|
|
||||||
val closureCallExports = mutableMapOf<IrSimpleType, IrSimpleFunction>()
|
val closureCallExports = mutableMapOf<String, IrSimpleFunction>()
|
||||||
val kotlinClosureToJsConverters = mutableMapOf<IrSimpleType, IrSimpleFunction>()
|
val kotlinClosureToJsConverters = mutableMapOf<String, IrSimpleFunction>()
|
||||||
val jsClosureCallers = mutableMapOf<IrSimpleType, IrSimpleFunction>()
|
val jsClosureCallers = mutableMapOf<String, IrSimpleFunction>()
|
||||||
val jsToKotlinClosures = mutableMapOf<IrSimpleType, IrSimpleFunction>()
|
val jsToKotlinClosures = mutableMapOf<String, IrSimpleFunction>()
|
||||||
|
|
||||||
val jsModuleAndQualifierReferences =
|
val jsModuleAndQualifierReferences =
|
||||||
mutableSetOf<JsModuleAndQualifierReference>()
|
mutableSetOf<JsModuleAndQualifierReference>()
|
||||||
|
|||||||
+51
-23
@@ -10,11 +10,11 @@ import org.jetbrains.kotlin.backend.common.DeclarationTransformer
|
|||||||
import org.jetbrains.kotlin.backend.common.ir.addDispatchReceiver
|
import org.jetbrains.kotlin.backend.common.ir.addDispatchReceiver
|
||||||
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
|
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
|
||||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||||
import org.jetbrains.kotlin.backend.common.serialization.cityHash64
|
|
||||||
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
|
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
|
||||||
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isBuiltInWasmRefType
|
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isBuiltInWasmRefType
|
||||||
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isExported
|
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isExported
|
||||||
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isExternalType
|
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isExternalType
|
||||||
|
import org.jetbrains.kotlin.backend.wasm.ir2wasm.toJsStringLiteral
|
||||||
import org.jetbrains.kotlin.backend.wasm.utils.getJsFunAnnotation
|
import org.jetbrains.kotlin.backend.wasm.utils.getJsFunAnnotation
|
||||||
import org.jetbrains.kotlin.backend.wasm.utils.getWasmImportDescriptor
|
import org.jetbrains.kotlin.backend.wasm.utils.getWasmImportDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||||
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.ir.types.*
|
|||||||
import org.jetbrains.kotlin.ir.util.*
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
|
|
||||||
@@ -279,7 +280,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
// Thus, we export helper "caller" method that JavaScript will use to call kotlin closures:
|
// Thus, we export helper "caller" method that JavaScript will use to call kotlin closures:
|
||||||
//
|
//
|
||||||
// @JsExport
|
// @JsExport
|
||||||
// fun __callFunction_<signatureHash>(f: dataref, p1: JsType1, p2: JsType2, ...): JsTypeRes {
|
// fun __callFunction_<signatureString>(f: dataref, p1: JsType1, p2: JsType2, ...): JsTypeRes {
|
||||||
// return adapt(
|
// return adapt(
|
||||||
// cast<FunctionN>(f).invoke(
|
// cast<FunctionN>(f).invoke(
|
||||||
// adapt(p1),
|
// adapt(p1),
|
||||||
@@ -289,7 +290,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
context.closureCallExports.getOrPut(this) {
|
context.closureCallExports.getOrPut(functionTypeInfo.signatureString) {
|
||||||
createKotlinClosureCaller(functionTypeInfo)
|
createKotlinClosureCaller(functionTypeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,11 +298,11 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
// using above-mentioned "caller" export:
|
// using above-mentioned "caller" export:
|
||||||
//
|
//
|
||||||
// @JsFun("""(f) => {
|
// @JsFun("""(f) => {
|
||||||
// (p1, p2, ...) => <wasm-exports>.__callFunction_<signatureHash>(f, p1, p2, ...)
|
// (p1, p2, ...) => <wasm-exports>.__callFunction_<signatureString>(f, p1, p2, ...)
|
||||||
// }""")
|
// }""")
|
||||||
// external fun __convertKotlinClosureToJsClosure_<signatureHash>(f: dataref): ExternalRef
|
// external fun __convertKotlinClosureToJsClosure_<signatureString>(f: dataref): ExternalRef
|
||||||
//
|
//
|
||||||
val kotlinToJsClosureConvertor = context.kotlinClosureToJsConverters.getOrPut(this) {
|
val kotlinToJsClosureConvertor = context.kotlinClosureToJsConverters.getOrPut(functionTypeInfo.signatureString) {
|
||||||
createKotlinToJsClosureConvertor(functionTypeInfo)
|
createKotlinToJsClosureConvertor(functionTypeInfo)
|
||||||
}
|
}
|
||||||
return FunctionBasedAdapter(kotlinToJsClosureConvertor)
|
return FunctionBasedAdapter(kotlinToJsClosureConvertor)
|
||||||
@@ -402,21 +403,21 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
// Thus, we import helper "caller" method that WebAssembly will use to call JS closures:
|
// Thus, we import helper "caller" method that WebAssembly will use to call JS closures:
|
||||||
//
|
//
|
||||||
// @JsFun("(f, p0, p1, ...) => f(p0, p1, ...)")
|
// @JsFun("(f, p0, p1, ...) => f(p0, p1, ...)")
|
||||||
// external fun __callJsClosure_<signatureHash>(f: ExternalRef, p0: JsType1, p1: JsType2, ...): JsResType
|
// external fun __callJsClosure_<signatureString>(f: ExternalRef, p0: JsType1, p1: JsType2, ...): JsResType
|
||||||
//
|
//
|
||||||
val jsClosureCaller = context.jsClosureCallers.getOrPut(this) {
|
val jsClosureCaller = context.jsClosureCallers.getOrPut(functionTypeInfo.signatureString) {
|
||||||
createJsClosureCaller(functionTypeInfo)
|
createJsClosureCaller(functionTypeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converter functions creates new Kotlin closure that delegate to JS closure
|
// Converter functions creates new Kotlin closure that delegate to JS closure
|
||||||
// using above-mentioned "caller" import:
|
// using above-mentioned "caller" import:
|
||||||
//
|
//
|
||||||
// fun __convertJsClosureToKotlinClosure_<signatureHash>(f: ExternalRef) : FunctionN<KotlinType1, ..., KotlinResType> =
|
// fun __convertJsClosureToKotlinClosure_<signatureString>(f: ExternalRef) : FunctionN<KotlinType1, ..., KotlinResType> =
|
||||||
// { p0: KotlinType1, p1: KotlinType2, ... ->
|
// { p0: KotlinType1, p1: KotlinType2, ... ->
|
||||||
// adapt(__callJsClosure_<signatureHash>(f, adapt(p0), adapt(p1), ..))
|
// adapt(__callJsClosure_<signatureString>(f, adapt(p0), adapt(p1), ..))
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
val jsToKotlinClosure = context.jsToKotlinClosures.getOrPut(this) {
|
val jsToKotlinClosure = context.jsToKotlinClosures.getOrPut(functionTypeInfo.signatureString) {
|
||||||
createJsToKotlinClosureConverter(functionTypeInfo, jsClosureCaller)
|
createJsToKotlinClosureConverter(functionTypeInfo, jsClosureCaller)
|
||||||
}
|
}
|
||||||
return FunctionBasedAdapter(jsToKotlinClosure)
|
return FunctionBasedAdapter(jsToKotlinClosure)
|
||||||
@@ -427,7 +428,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
|
|
||||||
private fun createKotlinClosureCaller(info: FunctionTypeInfo): IrSimpleFunction {
|
private fun createKotlinClosureCaller(info: FunctionTypeInfo): IrSimpleFunction {
|
||||||
val result = context.irFactory.buildFun {
|
val result = context.irFactory.buildFun {
|
||||||
name = Name.identifier("__callFunction_${info.hashString}")
|
name = Name.identifier("__callFunction_${info.signatureString}")
|
||||||
returnType = info.adaptedResultType
|
returnType = info.adaptedResultType
|
||||||
}
|
}
|
||||||
result.parent = currentParent
|
result.parent = currentParent
|
||||||
@@ -464,7 +465,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
|
|
||||||
private fun createKotlinToJsClosureConvertor(info: FunctionTypeInfo): IrSimpleFunction {
|
private fun createKotlinToJsClosureConvertor(info: FunctionTypeInfo): IrSimpleFunction {
|
||||||
val result = context.irFactory.buildFun {
|
val result = context.irFactory.buildFun {
|
||||||
name = Name.identifier("__convertKotlinClosureToJsClosure_${info.hashString}")
|
name = Name.identifier("__convertKotlinClosureToJsClosure_${info.signatureString}")
|
||||||
returnType = context.wasmSymbols.jsAnyType
|
returnType = context.wasmSymbols.jsAnyType
|
||||||
isExternal = true
|
isExternal = true
|
||||||
}
|
}
|
||||||
@@ -479,9 +480,9 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
val jsCode = buildString {
|
val jsCode = buildString {
|
||||||
append("(f) => (")
|
append("(f) => (")
|
||||||
appendParameterList(arity)
|
appendParameterList(arity)
|
||||||
append(") => wasmExports.__callFunction_")
|
append(") => wasmExports[")
|
||||||
append(info.hashString)
|
append("__callFunction_${info.signatureString}".toJsStringLiteral())
|
||||||
append("(f, ")
|
append("](f, ")
|
||||||
appendParameterList(arity)
|
appendParameterList(arity)
|
||||||
append(")")
|
append(")")
|
||||||
}
|
}
|
||||||
@@ -500,7 +501,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
): IrSimpleFunction {
|
): IrSimpleFunction {
|
||||||
val functionType = info.functionType
|
val functionType = info.functionType
|
||||||
val result = context.irFactory.buildFun {
|
val result = context.irFactory.buildFun {
|
||||||
name = Name.identifier("__convertJsClosureToKotlinClosure_${info.hashString}")
|
name = Name.identifier("__convertJsClosureToKotlinClosure_${info.signatureString}")
|
||||||
returnType = functionType
|
returnType = functionType
|
||||||
}
|
}
|
||||||
result.parent = currentParent
|
result.parent = currentParent
|
||||||
@@ -510,7 +511,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
}
|
}
|
||||||
|
|
||||||
val closureClass = context.irFactory.buildClass {
|
val closureClass = context.irFactory.buildClass {
|
||||||
name = Name.identifier("__JsClosureToKotlinClosure_${info.hashString}")
|
name = Name.identifier("__JsClosureToKotlinClosure_${info.signatureString}")
|
||||||
}.apply {
|
}.apply {
|
||||||
createImplicitParameterDeclarationWithWrappedDescriptor()
|
createImplicitParameterDeclarationWithWrappedDescriptor()
|
||||||
superTypes = listOf(functionType)
|
superTypes = listOf(functionType)
|
||||||
@@ -581,7 +582,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
|
|
||||||
private fun createJsClosureCaller(info: FunctionTypeInfo): IrSimpleFunction {
|
private fun createJsClosureCaller(info: FunctionTypeInfo): IrSimpleFunction {
|
||||||
val result = context.irFactory.buildFun {
|
val result = context.irFactory.buildFun {
|
||||||
name = Name.identifier("__callJsClosure_${info.hashString}")
|
name = Name.identifier("__callJsClosure_${info.signatureString}")
|
||||||
returnType = info.adaptedResultType
|
returnType = info.adaptedResultType
|
||||||
isExternal = true
|
isExternal = true
|
||||||
}
|
}
|
||||||
@@ -621,10 +622,6 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val hashString: String =
|
|
||||||
// Rendering type to improve hash stability across compiler runs
|
|
||||||
functionType.render().cityHash64().toULong().toString(Character.MAX_RADIX)
|
|
||||||
|
|
||||||
val originalParameterTypes: List<IrType> =
|
val originalParameterTypes: List<IrType> =
|
||||||
functionType.arguments.dropLast(1).map { (it as IrTypeProjection).type }
|
functionType.arguments.dropLast(1).map { (it as IrTypeProjection).type }
|
||||||
|
|
||||||
@@ -652,6 +649,37 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
|
|
||||||
val adaptedResultType: IrType =
|
val adaptedResultType: IrType =
|
||||||
(if (toJs) resultAdapter?.toType else resultAdapter?.fromType) ?: originalResultType
|
(if (toJs) resultAdapter?.toType else resultAdapter?.fromType) ?: originalResultType
|
||||||
|
|
||||||
|
val signatureString: String = jsInteropNotNullTypeSignature(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun jsInteropNotNullTypeSignature(type: JsInteropFunctionsLowering.FunctionTypeInfo): String {
|
||||||
|
val parameterTypes = type.originalParameterTypes.joinToString(separator = ",") { jsInteropTypeSignature(it) }
|
||||||
|
val resultType = jsInteropTypeSignature(type.originalResultType)
|
||||||
|
return "(($parameterTypes)->$resultType)"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun jsInteropNotNullTypeSignature(type: IrType): String {
|
||||||
|
if (isExternalType(type)) {
|
||||||
|
return "Js"
|
||||||
|
}
|
||||||
|
require(type is IrSimpleType)
|
||||||
|
if (type.isFunction()) {
|
||||||
|
return jsInteropNotNullTypeSignature(FunctionTypeInfo(type, true))
|
||||||
|
}
|
||||||
|
val klass = type.classOrNull ?: error("Unsupported JS interop type: ${type.render()}")
|
||||||
|
if (klass.owner.packageFqName == FqName("kotlin")) {
|
||||||
|
return klass.owner.name.identifier
|
||||||
|
}
|
||||||
|
error("Unsupported JS interop type: ${type.render()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun jsInteropTypeSignature(type: IrType): String {
|
||||||
|
return if (type.isNullable()) {
|
||||||
|
jsInteropNotNullTypeSignature(type.makeNotNull()) + "?"
|
||||||
|
} else {
|
||||||
|
jsInteropNotNullTypeSignature(type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InteropTypeAdapter {
|
interface InteropTypeAdapter {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// TARGET_BACKEND: WASM
|
||||||
|
|
||||||
|
// Type of functions `(T) -> T` are rendered the same way in IrType::render
|
||||||
|
fun <T : JsAny?> foo(f: (T) -> T, x: Int): Int = js("f(x)")
|
||||||
|
fun <T : JsAny?> foo(f: (T) -> T, x: String): String = js("f(x)")
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
if (foo<JsAny>({ it }, 10) != 10) return "Fail 1"
|
||||||
|
if (foo<JsAny>({ it }, "20") != "20") return "Fail 2"
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
+5
@@ -105,6 +105,11 @@ public class IrCodegenWasmJsInteropWasmTestGenerated extends AbstractIrCodegenWa
|
|||||||
runTest("compiler/testData/codegen/boxWasmJsInterop/kotlinToJsAdapters.kt");
|
runTest("compiler/testData/codegen/boxWasmJsInterop/kotlinToJsAdapters.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("lambdaAdapterNameClash.kt")
|
||||||
|
public void testLambdaAdapterNameClash() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/boxWasmJsInterop/lambdaAdapterNameClash.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("longStrings.kt")
|
@TestMetadata("longStrings.kt")
|
||||||
public void testLongStrings() throws Exception {
|
public void testLongStrings() throws Exception {
|
||||||
runTest("compiler/testData/codegen/boxWasmJsInterop/longStrings.kt");
|
runTest("compiler/testData/codegen/boxWasmJsInterop/longStrings.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user