[Wasm] Imporove external interface support

* Support boxing/unboxing when casting to Any
* Support ===, equals, hashCode, toString

* Support adapting String in interop boundary
This commit is contained in:
Svyatoslav Kuzmich
2021-10-15 23:04:22 +03:00
parent baa53b5cf3
commit 4fc461a2ff
30 changed files with 1240 additions and 81 deletions
@@ -35,7 +35,6 @@ class WasmBackendContext(
override val irBuiltIns: IrBuiltIns,
symbolTable: SymbolTable,
irModuleFragment: IrModuleFragment,
val additionalExportedDeclarations: Set<FqName>,
override val configuration: CompilerConfiguration,
) : JsCommonBackendContext {
override val builtIns = module.builtIns
@@ -66,7 +66,7 @@ private val expectDeclarationsRemovingPhase = makeWasmModulePhase(
description = "Remove expect declaration from module fragment"
)
private val stringConstructorLowering = makeWasmModulePhase(
private val stringConcatenationLowering = makeWasmModulePhase(
::StringConcatenationLowering,
name = "StringConcatenation",
description = "String concatenation lowering"
@@ -122,6 +122,18 @@ private val tailrecLoweringPhase = makeWasmModulePhase(
description = "Replace `tailrec` call sites with equivalent loop"
)
private val jsInteropFunctionsLowering = makeWasmModulePhase(
::JsInteropFunctionsLowering,
name = "JsInteropFunctionsLowering",
description = "Create delegates for JS interop",
)
private val jsInteropFunctionCallsLowering = makeWasmModulePhase(
::JsInteropFunctionCallsLowering,
name = "JsInteropFunctionCallsLowering",
description = "Replace calls to delegates",
)
private val enumClassConstructorLoweringPhase = makeWasmModulePhase(
::EnumClassConstructorLowering,
name = "EnumClassConstructorLowering",
@@ -134,7 +146,6 @@ private val enumClassConstructorBodyLoweringPhase = makeWasmModulePhase(
description = "Transform Enum Class into regular Class"
)
private val enumEntryInstancesLoweringPhase = makeWasmModulePhase(
::EnumEntryInstancesLowering,
name = "EnumEntryInstancesLowering",
@@ -431,6 +442,12 @@ private val objectUsageLoweringPhase = makeWasmModulePhase(
description = "Transform IrGetObjectValue into instance generator call"
)
private val explicitlyCastExternalTypesPhase = makeWasmModulePhase(
::ExplicitlyCastExternalTypesLowering,
name = "ExplicitlyCastExternalTypesLowering",
description = "Add explicit casts when converting between external and non-external types"
)
private val typeOperatorLoweringPhase = makeWasmModulePhase(
::WasmTypeOperatorLowering,
name = "TypeOperatorLowering",
@@ -515,6 +532,9 @@ val wasmPhases = NamedCompilerPhase(
delegateToPrimaryConstructorLoweringPhase then
// Common prefix ends
jsInteropFunctionsLowering then
jsInteropFunctionCallsLowering then
enumEntryInstancesLoweringPhase then
enumEntryInstancesBodyLoweringPhase then
enumClassCreateInitializerLoweringPhase then
@@ -536,7 +556,7 @@ val wasmPhases = NamedCompilerPhase(
forLoopsLoweringPhase then
propertyAccessorInlinerLoweringPhase then
stringConstructorLowering then
stringConcatenationLowering then
defaultArgumentStubGeneratorPhase then
defaultArgumentPatchOverridesPhase then
@@ -564,6 +584,7 @@ val wasmPhases = NamedCompilerPhase(
builtInsLoweringPhase0 then
autoboxingTransformerPhase then
explicitlyCastExternalTypesPhase then
objectUsageLoweringPhase then
typeOperatorLoweringPhase then
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.types.typeWith
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -157,9 +158,7 @@ class WasmSymbols(
val nullableFloatIeee754Equals = getInternalFunction("nullableFloatIeee754Equals")
val nullableDoubleIeee754Equals = getInternalFunction("nullableDoubleIeee754Equals")
val exportString = getInternalFunction("exportString")
val unsafeGetScratchRawMemory = getInternalFunction("unsafeGetScratchRawMemory")
val startCoroutineUninterceptedOrReturnIntrinsics =
(0..2).map { getInternalFunction("startCoroutineUninterceptedOrReturnIntrinsic$it") }
@@ -206,6 +205,29 @@ class WasmSymbols(
}
}
private val wasmDataRefClass = getIrClass(FqName("kotlin.wasm.internal.reftypes.dataref"))
val wasmDataRefType by lazy { wasmDataRefClass.defaultType }
inner class JsInteropAdapters {
val kotlinToJsStringAdapter = getInternalFunction("kotlinToJsStringAdapter")
val kotlinToJsBooleanAdapter = getInternalFunction("kotlinToJsBooleanAdapter")
val kotlinToJsAnyAdapter = getInternalFunction("kotlinToJsAnyAdapter")
val jsToKotlinAnyAdapter = getInternalFunction("jsToKotlinAnyAdapter")
val jsToKotlinStringAdapter = getInternalFunction("jsToKotlinStringAdapter")
val jsToKotlinByteAdapter = getInternalFunction("jsToKotlinByteAdapter")
val jsToKotlinShortAdapter = getInternalFunction("jsToKotlinShortAdapter")
val jsToKotlinCharAdapter = getInternalFunction("jsToKotlinCharAdapter")
}
val jsInteropAdapters = JsInteropAdapters()
private val jsExportClass = getIrClass(FqName("kotlin.js.JsExport"))
val jsExportConstructor by lazy { jsExportClass.constructors.single() }
private val jsNameClass = getIrClass(FqName("kotlin.js.JsName"))
val jsNameConstructor by lazy { jsNameClass.constructors.single() }
private fun findClass(memberScope: MemberScope, name: Name): ClassDescriptor =
memberScope.getContributedClassifier(name, NoLookupLocation.FROM_BACKEND) as ClassDescriptor
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmCompiledModuleFragment
import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmModuleFragmentGenerator
import org.jetbrains.kotlin.backend.wasm.lower.markExportedDeclarations
import org.jetbrains.kotlin.ir.backend.js.MainModule
import org.jetbrains.kotlin.ir.backend.js.ModulesStructure
import org.jetbrains.kotlin.ir.backend.js.loadIr
@@ -45,7 +46,7 @@ fun compileWasm(
}
val moduleDescriptor = moduleFragment.descriptor
val context = WasmBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, exportedDeclarations, configuration)
val context = WasmBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, configuration)
// Load declarations referenced during `context` initialization
allModules.forEach {
@@ -63,6 +64,8 @@ fun compileWasm(
deserializer.postProcess()
symbolTable.noUnboundLeft("Unbound symbols at the end of linker")
moduleFragment.files.forEach { irFile -> markExportedDeclarations(context, irFile, exportedDeclarations) }
wasmPhases.invokeToplevel(phaseConfig, context, moduleFragment)
val compiledWasmModule = WasmCompiledModuleFragment(context.irBuiltIns)
@@ -89,9 +92,12 @@ fun compileWasm(
fun WasmCompiledModuleFragment.generateJs(): String {
//language=js
val runtime = """
var wasmInstance = null;
const externrefBoxes = new WeakMap();
const runtime = {
identity(x) {
return x;
@@ -185,18 +185,6 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV
else
context.referenceLocal(valueSymbol)
)
if (valueSymbol.owner is IrValueParameter) {
val parent = valueDeclaration.parent
if (parent is IrFunction && parent.isExported(backendContext)) {
val type = context.transformType(valueDeclaration.type)
if (type is WasmRefNullType) {
// TODO: Add these casts as IR-2-IR lowering instead
generateTypeRTT(valueDeclaration.type)
body.buildRefCast()
}
}
}
}
override fun visitSetValue(expression: IrSetValue) {
@@ -324,16 +312,6 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV
body.buildCall(context.referenceFunction(function.symbol))
}
// 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 || function.getJsFunAnnotation() != null || function.isExternal || function.isExported(backendContext)) {
val resT = context.transformResultType(function.returnType)
if (resT is WasmRefNullType) {
generateTypeRTT(function.returnType)
body.buildRefCast()
}
}
// Unit types don't cross function boundaries
if (function.returnType == irBuiltIns.unitType)
body.buildGetUnit()
@@ -460,14 +438,6 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV
body.buildGetLocal(context.referenceLocal(0))
}
// Handle complex exported parameters.
// TODO: This should live as a separate lowering which creates separate shims for each exported function.
if (context.irFunction.isExported(context.backendContext) &&
expression.value.type.getClass() == irBuiltIns.stringType.getClass()) {
body.buildCall(context.referenceFunction(wasmSymbols.exportString))
}
body.buildInstr(WasmOp.RETURN)
}
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.backend.js.utils.isJsExport
import org.jetbrains.kotlin.ir.backend.js.utils.findUnitGetInstanceFunction
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
@@ -69,33 +70,18 @@ class DeclarationGenerator(val context: WasmModuleCodegenContext) : IrElementVis
// Generate function type
val watName = declaration.fqNameWhenAvailable.toString()
val irParameters = declaration.getEffectiveValueParameters()
// TODO: Exported types should be transformed in a separate lowering by creating shim functions for each export.
val isExported = declaration.isExported(context.backendContext)
val resultType =
when {
isExported -> context.transformExportedResultType(declaration.returnType)
// Unit_getInstance returns true Unit reference instead of "void"
declaration == unitGetInstanceFunction -> context.transformType(declaration.returnType)
else -> context.transformResultType(declaration.returnType)
}
val isExportedOrImported = isExported || importedName != null
val wasmFunctionType =
WasmFunctionType(
name = watName,
parameterTypes = irParameters.map {
val t = context.transformValueParameterType(it)
if (isExportedOrImported && t is WasmRefNullType) {
WasmRefNullType(WasmHeapType.Simple.Data)
} else {
t
}
},
resultTypes = listOfNotNull(
resultType.let {
if (isExportedOrImported && it is WasmRefNullType) WasmRefNullType(WasmHeapType.Simple.Data) else it
}
)
parameterTypes = irParameters.map { context.transformValueParameterType(it) },
resultTypes = listOfNotNull(resultType)
)
context.defineFunctionType(declaration.symbol, wasmFunctionType)
@@ -164,12 +150,11 @@ class DeclarationGenerator(val context: WasmModuleCodegenContext) : IrElementVis
if (initPriority != null)
context.registerInitFunction(function, initPriority)
if (declaration.isExported(backendContext)) {
if (declaration.isExported()) {
context.addExport(
WasmExport.Function(
field = function,
// TODO: Add ability to specify exported name.
name = declaration.name.identifier
name = declaration.getJsNameOrKotlinName().identifier
)
)
}
@@ -371,7 +356,7 @@ fun generateDefaultInitializerForType(type: WasmType, g: WasmExpressionBuilder)
WasmF32 -> g.buildConstF32(0f)
WasmF64 -> g.buildConstF64(0.0)
is WasmRefNullType -> g.buildRefNull(type.heapType)
is WasmExternRef -> g.buildRefNull(WasmHeapType.Simple.Extern)
is WasmExternRef, is WasmAnyRef -> g.buildRefNull(WasmHeapType.Simple.Extern)
WasmUnreachableType -> error("Unreachable type can't be initialized")
else -> error("Unknown value type ${type.name}")
}
@@ -381,5 +366,5 @@ fun IrFunction.getEffectiveValueParameters(): List<IrValueParameter> {
return listOfNotNull(implicitThis, dispatchReceiverParameter, extensionReceiverParameter) + valueParameters
}
fun IrFunction.isExported(context: WasmBackendContext): Boolean =
fqNameWhenAvailable in context.additionalExportedDeclarations || isJsExport()
fun IrFunction.isExported(): Boolean =
isJsExport()
@@ -12,9 +12,12 @@ import org.jetbrains.kotlin.ir.declarations.IrTypeParameter
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.ir.util.packageFqName
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.wasm.ir.*
class WasmTypeTransformer(
@@ -93,13 +96,21 @@ class WasmTypeTransformer(
symbols.voidType ->
error("Void type can't be used as a value")
// this also handles builtIns.stringType
else -> {
val klass = this.getClass()
val ic = context.backendContext.inlineClassesUtils.getInlinedClass(this)
if (klass != null && (klass.hasWasmForeignAnnotation() || klass.isExternal)) {
WasmExternRef
WasmAnyRef
} else if (klass != null && isBuiltInWasmRefType(this)) {
when (val name = klass.name.identifier) {
"anyref" -> WasmAnyRef
"eqref" -> WasmEqRef
"dataref" -> WasmRefNullType(WasmHeapType.Simple.Data)
"i31ref" -> WasmI31Ref
"funcref" -> WasmRefNullType(WasmHeapType.Simple.Func)
else -> error("Unknown reference type $name")
}
} else if (ic != null) {
context.backendContext.inlineClassesUtils.getInlineClassUnderlyingType(ic).toWasmValueType()
} else {
@@ -109,6 +120,12 @@ class WasmTypeTransformer(
}
}
fun isBuiltInWasmRefType(type: IrType): Boolean {
return type.classOrNull?.owner?.packageFqName == FqName("kotlin.wasm.internal.reftypes")
}
fun isExternalType(type: IrType): Boolean =
type.erasedUpperBound?.isExternal ?: false
// Return null if upper bound is Any
private val IrTypeParameter.erasedUpperBound: IrClass?
@@ -43,7 +43,6 @@ interface WasmBaseCodegenContext {
fun transformBoxedType(irType: IrType): WasmType
fun transformValueParameterType(irValueParameter: IrValueParameter): WasmType
fun transformResultType(irType: IrType): WasmType?
fun transformExportedResultType(irType: IrType): WasmType?
fun transformBlockResultType(irType: IrType): WasmType?
@@ -58,13 +58,6 @@ class WasmModuleCodegenContextImpl(
return with(typeTransformer) { irType.toWasmResultType() }
}
override fun transformExportedResultType(irType: IrType): WasmType? {
// Exported strings are passed as pointers to the raw memory
if (irType.getClass() == backendContext.irBuiltIns.stringType.getClass())
return WasmI32
return with(typeTransformer) { irType.toWasmResultType() }
}
override fun transformBlockResultType(irType: IrType): WasmType? {
return with(typeTransformer) { irType.toWasmBlockResultType() }
}
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2021 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 org.jetbrains.kotlin.backend.wasm.lower
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isExternalType
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.lower.AbstractValueUsageLowering
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.types.IrType
/**
* Insert casts between external and non-external types
*/
class ExplicitlyCastExternalTypesLowering(wasmContext: WasmBackendContext) : AbstractValueUsageLowering(wasmContext) {
override fun IrExpression.useExpressionAsType(actualType: IrType, expectedType: IrType): IrExpression {
val expectedExternal = isExternalType(expectedType)
val actualExternal = isExternalType(actualType)
if (expectedExternal != actualExternal) {
return JsIrBuilder.buildImplicitCast(this, toType = expectedType)
}
return this
}
}
@@ -0,0 +1,308 @@
/*
* Copyright 2010-2021 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 org.jetbrains.kotlin.backend.wasm.lower
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.backend.common.DeclarationTransformer
import org.jetbrains.kotlin.backend.common.ir.createStaticFunctionWithReceivers
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isBuiltInWasmRefType
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isExported
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isExternalType
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrBody
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
/**
* Create wrappers for external and @JsExport functions when type adaptation is needed
*/
class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationTransformer {
val builtIns = context.irBuiltIns
val symbols = context.wasmSymbols
val adapters = symbols.jsInteropAdapters
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (declaration.isFakeOverride) return null
if (declaration !is IrSimpleFunction) return null
val isExported = declaration.isExported()
val isExternal = declaration.isExternal
if (!isExported && !isExternal) return null
check(!(isExported && isExternal)) { "Exported external declarations are not supported: ${declaration.fqNameWhenAvailable}" }
check(declaration.parent !is IrClass) { "Interop members are not supported: ${declaration.fqNameWhenAvailable}" }
return if (isExternal)
transformExternalFunction(declaration)
else
transformExportFunction(declaration)
}
/**
* external fun foo(x: KotlinType): KotlinType
*
* ->
*
* external fun foo(x: JsType): JsType
* fun foo__externalAdapter(x: KotlinType): KotlinType = adaptResult(foo(adaptParameter(x)));
*/
fun transformExternalFunction(function: IrSimpleFunction): List<IrDeclaration>? {
val valueParametersAdapters = function.valueParameters.map {
it.type.kotlinToJsAdapterIfNeeded(isReturn = false)
}
val resultAdapter =
function.returnType.jsToKotlinAdapterIfNeeded(isReturn = true)
if (resultAdapter == null && valueParametersAdapters.all { it == null })
return null
val newFun = context.irFactory.createStaticFunctionWithReceivers(
function.parent,
name = Name.identifier(function.name.asStringStripSpecialMarkers() + "__externalAdapter"),
function
)
function.valueParameters.forEachIndexed { index, newParameter ->
val adapter = valueParametersAdapters[index]
if (adapter != null) {
newParameter.type = adapter.toType
}
}
resultAdapter?.let {
function.returnType = resultAdapter.fromType
}
val builder = context.createIrBuilder(newFun.symbol)
newFun.body = createAdapterFunctionBody(builder, newFun, function, valueParametersAdapters, resultAdapter)
newFun.annotations = emptyList()
context.mapping.wasmJsInteropFunctionToWrapper[function] = newFun
return listOf(function, newFun)
}
/**
* @JsExport
* fun foo(x: KotlinType): KotlinType { <original-body> }
*
* ->
*
* @JsExport
* @JsName("foo")
* fun foo__JsExportAdapter(x: JsType): JsType =
* adaptResult(foo(adaptParameter(x)));
*
* fun foo(x: KotlinType): KotlinType { <original-body> }
*/
fun transformExportFunction(function: IrSimpleFunction): List<IrDeclaration>? {
val valueParametersAdapters = function.valueParameters.map {
it.type.jsToKotlinAdapterIfNeeded(isReturn = false)
}
val resultAdapter =
function.returnType.kotlinToJsAdapterIfNeeded(isReturn = true)
if (resultAdapter == null && valueParametersAdapters.all { it == null })
return null
val newFun = context.irFactory.createStaticFunctionWithReceivers(
function.parent,
name = Name.identifier(function.name.asStringStripSpecialMarkers() + "__JsExportAdapter"),
function
)
newFun.valueParameters.forEachIndexed { index, newParameter ->
val adapter = valueParametersAdapters[index]
if (adapter != null) {
newParameter.type = adapter.fromType
}
}
resultAdapter?.let {
newFun.returnType = resultAdapter.toType
}
// Delegate new function to old function:
val builder: DeclarationIrBuilder = context.createIrBuilder(newFun.symbol)
newFun.body = createAdapterFunctionBody(builder, newFun, function, valueParametersAdapters, resultAdapter)
newFun.annotations += builder.irCallConstructor(context.wasmSymbols.jsNameConstructor, typeArguments = emptyList()).also {
it.putValueArgument(0, builder.irString(function.getJsNameOrKotlinName().identifier))
}
function.annotations = function.annotations.filter { it.symbol != context.wasmSymbols.jsExportConstructor }
return listOf(function, newFun)
}
private fun createAdapterFunctionBody(
builder: DeclarationIrBuilder,
function: IrSimpleFunction,
functionToCall: IrSimpleFunction,
valueParametersAdapters: List<InteropTypeAdapter?>,
resultAdapter: InteropTypeAdapter?
) = builder.irBlockBody {
+irReturn(
irCall(functionToCall).let { call ->
for ((index, valueParameter) in function.valueParameters.withIndex()) {
val get = irGet(valueParameter)
call.putValueArgument(index, valueParametersAdapters[index]?.adapt(get, builder) ?: get)
}
resultAdapter?.adapt(call, builder) ?: call
}
)
}
private fun IrType.kotlinToJsAdapterIfNeeded(isReturn: Boolean): InteropTypeAdapter? {
if (isReturn && this == builtIns.unitType)
return null
when (this) {
builtIns.stringType -> return FunctionBasedAdapter(adapters.kotlinToJsStringAdapter.owner)
builtIns.stringType.makeNullable() -> return NullOrAdapter(FunctionBasedAdapter(adapters.kotlinToJsStringAdapter.owner))
builtIns.booleanType -> return FunctionBasedAdapter(adapters.kotlinToJsBooleanAdapter.owner)
builtIns.anyType -> return FunctionBasedAdapter(adapters.kotlinToJsAnyAdapter.owner)
builtIns.byteType,
builtIns.shortType,
builtIns.charType,
builtIns.intType,
builtIns.longType,
builtIns.floatType,
builtIns.doubleType,
context.wasmSymbols.voidType ->
return null
}
if (isExternalType(this))
return null
if (isBuiltInWasmRefType(this))
return null
return SendKotlinObjectToJsAdapter(this)
}
private fun IrType.jsToKotlinAdapterIfNeeded(isReturn: Boolean): InteropTypeAdapter? {
if (isReturn && this == builtIns.unitType)
return null
when (this) {
builtIns.stringType -> return FunctionBasedAdapter(adapters.jsToKotlinStringAdapter.owner)
builtIns.anyType -> return FunctionBasedAdapter(adapters.jsToKotlinAnyAdapter.owner)
builtIns.byteType -> return FunctionBasedAdapter(adapters.jsToKotlinByteAdapter.owner)
builtIns.shortType -> return FunctionBasedAdapter(adapters.jsToKotlinShortAdapter.owner)
builtIns.charType -> return FunctionBasedAdapter(adapters.jsToKotlinCharAdapter.owner)
builtIns.booleanType,
builtIns.intType,
builtIns.longType,
builtIns.floatType,
builtIns.doubleType,
context.wasmSymbols.voidType ->
return null
}
if (isExternalType(this))
return null
if (isBuiltInWasmRefType(this))
return null
return ReceivingKotlinObjectFromJsAdapter(this)
}
interface InteropTypeAdapter {
val fromType: IrType
val toType: IrType
fun adapt(expression: IrExpression, builder: IrBuilderWithScope): IrExpression
}
/**
* Adapter implemented as a single function call
*/
class FunctionBasedAdapter(
private val function: IrSimpleFunction,
) : InteropTypeAdapter {
override val fromType = function.valueParameters[0].type
override val toType = function.returnType
override fun adapt(expression: IrExpression, builder: IrBuilderWithScope): IrExpression {
val call = builder.irCall(function)
call.putValueArgument(0, expression)
return call
}
}
/**
* Current V8 Wasm GC mandates dataref type instead of structs and arrays
*/
inner class SendKotlinObjectToJsAdapter(
override val fromType: IrType
) : InteropTypeAdapter {
override val toType: IrType = context.wasmSymbols.wasmDataRefType
override fun adapt(expression: IrExpression, builder: IrBuilderWithScope): IrExpression {
return builder.irReinterpretCast(expression, toType)
}
}
/**
* Current V8 Wasm GC mandates dataref type instead of structs and arrays
*/
inner class ReceivingKotlinObjectFromJsAdapter(
override val toType: IrType
) : InteropTypeAdapter {
override val fromType: IrType = context.wasmSymbols.wasmDataRefType
override fun adapt(expression: IrExpression, builder: IrBuilderWithScope): IrExpression {
val call = builder.irCall(context.wasmSymbols.wasmRefCast)
call.putValueArgument(0, expression)
call.putTypeArgument(0, toType)
return call
}
}
/**
* Effectively `value?.let { adapter(it) }`
*/
inner class NullOrAdapter(
val adapter: InteropTypeAdapter
) : InteropTypeAdapter {
override val fromType: IrType = adapter.fromType.makeNullable()
override val toType: IrType = adapter.toType.makeNullable()
override fun adapt(expression: IrExpression, builder: IrBuilderWithScope): IrExpression {
return builder.irComposite {
val tmp = irTemporary(adapter.adapt(expression, builder))
+irIfNull(toType, irGet(tmp), irNull(toType), irImplicitCast(irGet(tmp), toType))
}
}
}
}
/**
* Redirect calls to external and @JsExport functions to created wrappers
*/
class JsInteropFunctionCallsLowering(val context: WasmBackendContext) : BodyLoweringPass {
override fun lower(irBody: IrBody, container: IrDeclaration) {
irBody.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitCall(expression: IrCall): IrExpression {
expression.transformChildrenVoid()
val newFun: IrSimpleFunction? = context.mapping.wasmJsInteropFunctionToWrapper[expression.symbol.owner]
return if (newFun != null && container != newFun) {
irCall(expression, newFun)
} else {
expression
}
}
})
}
}
@@ -152,6 +152,8 @@ class WasmBaseTypeOperatorTransformer(val context: WasmBackendContext) : IrEleme
private fun generateTypeCheckNonNull(argument: IrExpression, toType: IrType): IrExpression {
assert(!toType.isMarkedNullable())
return when {
// TODO: Use instanceof for classes later
toType.eraseToClassOrInterface.isExternal -> builder.irTrue()
toType.isNothing() -> builder.irFalse()
toType.isTypeParameter() -> generateTypeCheckWithTypeParameter(argument, toType)
toType.isInterface() -> generateIsInterface(argument, toType)
@@ -190,7 +192,28 @@ class WasmBaseTypeOperatorTransformer(val context: WasmBackendContext) : IrEleme
}
}
if (fromType.eraseToClassOrInterface.isSubclassOf(toType.eraseToClassOrInterface)) {
val fromClass = fromType.eraseToClassOrInterface
val toClass = toType.eraseToClassOrInterface
if (fromClass.isExternal && toClass.isExternal) {
return value
}
if (fromClass.isExternal && !toClass.isExternal) {
val narrowingToAny = builder.irCall(symbols.jsInteropAdapters.jsToKotlinAnyAdapter).also {
it.putValueArgument(0, value)
}
// Continue narrowing from Any to expected type
return narrowType(context.irBuiltIns.anyType, toType, narrowingToAny)
}
if (toClass.isExternal && !fromClass.isExternal) {
return builder.irCall(symbols.jsInteropAdapters.kotlinToJsAnyAdapter).also {
it.putValueArgument(0, value)
}
}
if (fromClass.isSubclassOf(toClass)) {
return value
}
if (toType.isNothing()) {
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2021 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 org.jetbrains.kotlin.backend.wasm.lower
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.ir.builders.irCallConstructor
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.name.FqName
/**
* Mark declarations from [exportedFqNames] with @JsExport annotation
*/
fun markExportedDeclarations(context: WasmBackendContext, irFile: IrFile, exportedFqNames: Set<FqName>) {
for (declaration in irFile.declarations) {
if (declaration is IrFunction && declaration.fqNameWhenAvailable in exportedFqNames) {
val builder = context.createIrBuilder(irFile.symbol)
declaration.annotations +=
builder.irCallConstructor(context.wasmSymbols.jsExportConstructor, typeArguments = emptyList())
}
}
}
@@ -40,6 +40,10 @@ class JsMapping(val state: JsMappingState) : DefaultMapping(state) {
state.newDeclarationToDeclarationMapping<IrSimpleFunction, IrSimpleFunction>()
val suspendArityStore = state.newDeclarationToDeclarationCollectionMapping<IrClass, Collection<IrSimpleFunction>>()
// Wasm mappings
val wasmJsInteropFunctionToWrapper =
state.newDeclarationToDeclarationMapping<IrSimpleFunction, IrSimpleFunction>()
}