[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
+3 -1
View File
@@ -1,6 +1,8 @@
<component name="ArtifactManager">
<artifact type="jar" name="kotlin-test-wasm-js-1.6.255-SNAPSHOT">
<output-path>$PROJECT_DIR$/libraries/kotlin.test/wasm/build/libs</output-path>
<root id="archive" name="kotlin-test-wasm-js-1.6.255-SNAPSHOT.jar" />
<root id="archive" name="kotlin-test-wasm-js-1.6.255-SNAPSHOT.jar">
<element id="module-output" name="kotlin.kotlin-test.kotlin-test-wasm.jsMain" />
</root>
</artifact>
</component>
@@ -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>()
}
@@ -0,0 +1,231 @@
// WITH_RUNTIME
// FILE: externals.js
const primitives1 = [3.14, "Test string 1", true, Symbol("symbol"), 131283889534859707199254740992n];
const primitives2 = [3.15, "Test string 2", false, Symbol("symbol"), 231283889534859707199254740992n];
function getPrimitive1(n) {
return primitives1[n];
}
function getPrimitive2(n) {
return primitives2[n];
}
function testPrimitive(obj, n) {
return obj === primitives1[n];
}
function getNewObject() {
return { value: 123 };
}
function testObject(obj) {
return obj.value === 123;
}
function getNewArray() {
return [1, 2, 3];
}
function testArray(obj) {
return Array.isArray(obj) && obj[0] === 1 && obj[1] === 2 && obj [2] === 3;
}
function roundTrip(x) { return x; }
// FILE: externals.kt
external interface Obj
external interface SubObj : Obj
external fun getPrimitive1(n: Int): Obj
external fun getPrimitive2(n: Int): Obj
external fun testPrimitive(obj: Obj, n: Int): Boolean
external fun getNewObject(): Obj
external fun testObject(obj: Obj): Boolean
external fun getNewArray(): Obj
external fun testArray(obj: Obj): Boolean
external fun roundTrip(x: Obj): Obj
fun assertTrue(x: Boolean) {
if (!x) error("assertTrue fail")
}
fun assertFalse(x: Boolean) {
if (x) error("assertFalse fail")
}
fun multiCast(obj: Obj): Obj =
obj
as Any as Any
as Any as? Any
as Any as Any?
as Any as? Any?
as Any as Obj
as Any as? Obj
as Any as Obj?
as Any as? Obj?
as Any? as Any
as Any? as? Any
as Any? as Any?
as Any? as? Any?
as Any? as Obj
as Any? as? Obj
as Any? as Obj?
as Any? as? Obj?
as Obj as Any
as Obj as? Any
as Obj as Any?
as Obj as? Any?
as Obj as Obj
as Obj as? Obj
as Obj as Obj?
as Obj as? Obj?
as Obj? as Any
as Obj? as? Any
as Obj? as Any?
as Obj? as? Any?
as Obj? as Obj
as Obj? as? Obj
as Obj? as Obj?
as Obj? as? Obj?
as Obj
class CustomClass
interface CustomInterface
fun <T1 : Any, T2 : Any?, T3 : Obj> testWithTypeParameters(obj: Obj, checkObj: (Any?) -> Unit) {
checkObj(obj as T1)
checkObj(obj as T2)
checkObj(obj as T3)
checkObj(obj as? T1)
checkObj(obj as? T2)
checkObj(obj as? T3)
checkObj(obj as T1?)
checkObj(obj as T2?)
checkObj(obj as T3?)
checkObj(obj as? T1?)
checkObj(obj as? T2?)
checkObj(obj as? T3?)
}
fun test(
obj: Obj,
anotherObj: Obj,
checkObj: (Any?) -> Unit,
stableIdentity: Boolean
) {
checkObj(obj)
checkObj(obj as Any)
checkObj(obj as? Any)
checkObj(obj as Any?)
checkObj(obj as? Any?)
testWithTypeParameters<Any, Any?, Obj>(obj, checkObj)
val subObj: SubObj = obj as SubObj
checkObj(subObj)
checkObj(subObj as Obj)
val roundTrippedObj = roundTrip(obj)
checkObj(roundTrippedObj)
assertTrue(obj == roundTrippedObj)
if (stableIdentity)
assertTrue(obj === roundTrippedObj)
val castedObj = multiCast(roundTrippedObj)
checkObj(castedObj)
assertTrue(obj is Any)
assertTrue(obj is Any?)
assertFalse(obj !is Any)
assertFalse(obj !is Any?)
assertFalse(obj as Any is CustomClass)
assertFalse(obj as Any is CustomInterface)
val objects = listOf<Obj>(obj, roundTrippedObj, castedObj)
for (obj1 in objects) {
checkObj(obj1)
assertTrue(obj1.hashCode() == obj.hashCode())
assertTrue(obj1.toString() == obj.toString())
assertTrue(obj != anotherObj)
assertTrue(obj !== anotherObj)
for (obj2 in objects) {
assertTrue(obj1 == obj2)
if (stableIdentity)
assertTrue(obj1 === obj2)
assertTrue(obj1.hashCode() == obj2.hashCode())
assertTrue(obj1.toString() == obj2.toString())
}
}
assertTrue(objects.size == 3)
assertTrue(objects.toSet().size == 1)
assertTrue((objects + anotherObj).toSet().size == 2)
}
fun testPrimitive(n: Int) {
test(
getPrimitive1(n),
getPrimitive2(n),
{ obj ->
if (!testPrimitive(obj as Obj, n)) {
error("Fail $n")
}
},
stableIdentity = false,
)
}
class C(val x: Int)
value class IC(val x: Int)
fun box(): String {
for (i in 0..2) { // TODO: Symbols and BigInts are not supprted in Wasm
testPrimitive(i)
}
test(
getNewObject(),
getNewObject(),
{ obj ->
if (!testObject(obj as Obj)) {
error("Fail object")
}
},
stableIdentity = true,
)
test(
getNewArray(),
getNewArray(),
{ obj ->
if (!testArray(obj as Obj)) {
error("Fail object")
}
},
stableIdentity = true,
)
val kotlinValues = listOf(10, "10", true, arrayOf(10), intArrayOf(20), C(10), IC(10))
val otherValues = listOf(11, "11", false, arrayOf(12), intArrayOf(22), C(11), IC(11))
for ((value, otherValue) in kotlinValues.zip(otherValues)) {
test(
value as Obj,
otherValues as Obj,
{ obj ->
if (obj !== value) {
error("Fail custom class")
}
},
stableIdentity = true,
)
}
return "OK"
}
+26
View File
@@ -9,6 +9,20 @@ fun makeC(x: Int): C = C(x)
@JsExport
fun getX(c: C): Int = c.x
@JsExport
fun getString(s: String): String = "Test string $s";
@JsExport
fun isEven(x: Int): Boolean = x % 2 == 0
external interface EI
@JsExport
fun eiAsAny(ei: EI): Any = ei
@JsExport
fun anyAsEI(any: Any): EI = any as EI
fun box(): String = "OK"
// FILE: jsExport__after.js
@@ -16,4 +30,16 @@ fun box(): String = "OK"
const c = main.makeC(300);
if (main.getX(c) !== 300) {
throw "Fail 1";
}
if (main.getString("2") !== "Test string 2") {
throw "Fail 2";
}
if (main.isEven(31) !== false || main.isEven(10) !== true) {
throw "Fail 3";
}
if (main.anyAsEI(main.eiAsAny({x:10})).x !== 10) {
throw "Fail 4";
}
@@ -0,0 +1,65 @@
// TARGET_BACKEND: WASM
// WITH_RUNTIME
// FILE: externals.js
function roundTrip(x) { return x; }
// FILE: externals.kt
external fun roundTrip(x: EI?): EI?
fun assertTrue(x: Boolean) {
if (!x) error("assertTrue fail")
}
fun assertFalse(x: Boolean) {
if (x) error("assertFalse fail")
}
external interface EI
@JsFun("() => null")
external fun getNull(): EI?
@JsFun("() => undefined")
external fun getUndefined(): EI?
@JsFun("(ref) => ref === null")
external fun isJsNull(ref: EI?): Boolean
@JsFun("(ref) => ref === undefined")
external fun isJsUndefined(ref: EI?): Boolean
@JsFun("() => null")
external fun getJsNullAsNonNullable(): EI
@JsFun("() => undefined")
external fun getJsUndefinedAsNonNullable(): EI
fun box(): String {
val jsNull = getNull()
val jsUndefined = getUndefined()
assertTrue(jsNull == null)
assertTrue((jsNull as Any?) == null)
assertTrue((jsNull as Any?) === null)
assertTrue(jsUndefined == null)
assertTrue(isJsNull(null))
assertTrue(isJsNull(null as EI?))
assertTrue(isJsNull(null as? EI?))
assertTrue(isJsNull(roundTrip(null)))
assertTrue(isJsNull(jsNull))
assertFalse(isJsUndefined(null))
assertTrue(isJsUndefined(jsUndefined))
// TODO: Should these fail?
val n2 = getJsNullAsNonNullable()
val ud2 = getJsUndefinedAsNonNullable()
assertTrue(isJsNull(n2))
assertTrue(isJsUndefined(ud2))
return "OK"
}
+112
View File
@@ -0,0 +1,112 @@
// FILE: externals.js
// -- Strings --
function isTestString(x) {
return x === "Test string";
}
function getTestString() {
return "Test string";
}
function concatStrings(x, y) {
if (typeof x !== "string") return "Fail 1";
if (typeof y !== "string") return "Fail 2";
return x + y;
}
function concatStringsNullable(x, y) {
return concatStrings(x ?? "<null>", y ?? "<null>")
}
// -- Booleans --
function isTrueBoolean(x) {
if (typeof x !== "boolean") return "Fail";
return x === true;
}
function isFalseBoolean(x) {
if (typeof x !== "boolean") return "Fail";
return x === false;
}
function getTrueBoolean(x) {
return true;
}
function getFalseBoolean(x) {
return false;
}
// -- Any --
function createJsObjectAsAny() {
return { value: "object created by createJsObjectAsAny" };
}
function createJsObjectAsExternalInterface() {
return { value: "object created by createJsObjectAsExternalInterface" };
}
function getObjectValueEI(x) {
return x.value;
}
function getObjectValueAny(x) {
return x.value;
}
// FILE: externals.kt
external fun isTestString(x: String): Boolean
external fun getTestString(): String
external fun concatStrings(x: String, y: String): String
//external fun concatStringsNullable(x: String?, y: String?): String?
external fun isTrueBoolean(x: Boolean): Boolean
external fun isFalseBoolean(x: Boolean): Boolean
external fun getTrueBoolean(): Boolean
external fun getFalseBoolean(): Boolean
external interface EI
external fun createJsObjectAsAny(): Any
external fun createJsObjectAsExternalInterface(): EI
external fun getObjectValueEI(x: EI): String
external fun getObjectValueAny(x: Any): String
fun box(): String {
// Strings
if (!isTestString("Test string")) return "Fail !isTestString"
if (isTestString("Test string 2")) return "Fail isTestString"
if (getTestString() != "Test string") return "Fail getTestString"
if (concatStrings("A", "B") != "AB") return "Fail concatStrings 1"
if (concatStrings("Привет ", "😀\uD83D") != "Привет 😀\uD83D") return "Fail concatStrings 2"
// if (concatStringsNullable("A", "B") != "AB") return "Fail concatStringsNullable 1"
// Boolean
if (!isTrueBoolean(true)) return "Fail !isTrueBoolean"
if (isTrueBoolean(false)) return "Fail isTrueBoolean"
if (!isFalseBoolean(false)) return "Fail !isFalseBoolean"
if (isFalseBoolean(true)) return "Fail isFalseBoolean"
if (getTrueBoolean() != true) return "Fail getTrueBoolean"
if (getFalseBoolean() != false) return "Fail getFalseBoolean"
// External interface
val objAsEI: EI = createJsObjectAsExternalInterface()
if (getObjectValueEI(objAsEI) != "object created by createJsObjectAsExternalInterface")
return "Fail createJsObjectAsExternalInterface + getObjectValueEI"
if (getObjectValueAny(objAsEI) != "object created by createJsObjectAsExternalInterface")
return "Fail createJsObjectAsExternalInterface + getObjectValueAny"
// Any
val objAsAny: Any = createJsObjectAsAny()
if (getObjectValueAny(objAsAny) != "object created by createJsObjectAsAny")
return "Fail createJsObjectAsAny + getObjectValueAny"
if (getObjectValueEI(objAsAny as EI) != "object created by createJsObjectAsAny")
return "Fail createJsObjectAsAny + getObjectValueEI"
return "OK"
}
@@ -195,7 +195,7 @@ abstract class BasicWasmBoxTest(
wasmInstance.exports.startUnitTests?.();
const actualResult = importStringFromWasm(wasmInstance.exports.$testFunction());
const actualResult = wasmInstance.exports.$testFunction();
if (actualResult !== "OK")
throw `Wrong box result '${'$'}{actualResult}'; Expected "OK"`;
""".trimIndent()
@@ -23,3 +23,9 @@ abstract class AbstractIrJsCodegenInlineTest : BasicIrBoxTest(
"compiler/testData/codegen/boxInline/",
"codegen/irBoxInline/"
)
abstract class AbstractIrCodegenWasmJsInteropJsTest : BasicIrBoxTest(
"compiler/testData/codegen/wasmJsInterop",
"codegen/wasmJsInteropJs"
)
@@ -24,11 +24,6 @@ abstract class AbstractJsCodegenInlineTest : BasicBoxTest(
"codegen/boxInline"
)
abstract class AbstractIrCodegenWasmJsInteropJsTest : BasicBoxTest(
"compiler/testData/codegen/wasmJsInterop",
"codegen/wasmJsInteropWasm"
)
abstract class AbstractJsLegacyPrimitiveArraysBoxTest : BasicBoxTest(
"compiler/testData/codegen/box/arrays/",
@@ -3,7 +3,7 @@
* 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.js.test.semantics;
package org.jetbrains.kotlin.js.test.ir.semantics;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
@@ -30,6 +30,11 @@ public class IrCodegenWasmJsInteropJsTestGenerated extends AbstractIrCodegenWasm
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/boxWasmJsInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true);
}
@TestMetadata("externalTypeOperators.kt")
public void testExternalTypeOperators() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/externalTypeOperators.kt");
}
@TestMetadata("externals.kt")
public void testExternals() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/externals.kt");
@@ -39,4 +44,9 @@ public class IrCodegenWasmJsInteropJsTestGenerated extends AbstractIrCodegenWasm
public void testJsExport() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/jsExport.kt");
}
@TestMetadata("types.kt")
public void testTypes() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/types.kt");
}
}
@@ -30,6 +30,11 @@ public class IrCodegenWasmJsInteropWasmTestGenerated extends AbstractIrCodegenWa
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/boxWasmJsInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.WASM, true);
}
@TestMetadata("externalTypeOperators.kt")
public void testExternalTypeOperators() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/externalTypeOperators.kt");
}
@TestMetadata("externals.kt")
public void testExternals() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/externals.kt");
@@ -39,4 +44,14 @@ public class IrCodegenWasmJsInteropWasmTestGenerated extends AbstractIrCodegenWa
public void testJsExport() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/jsExport.kt");
}
@TestMetadata("nullableExternRefs.kt")
public void testNullableExternRefs() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/nullableExternRefs.kt");
}
@TestMetadata("types.kt")
public void testTypes() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/types.kt");
}
}
@@ -0,0 +1,191 @@
/*
* 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 kotlin.wasm.internal
import kotlin.wasm.internal.reftypes.anyref
internal external interface ExternalInterfaceType
internal class JsExternalBox(val ref: ExternalInterfaceType) {
override fun toString(): String =
externrefToString(ref)
override fun equals(other: Any?): Boolean =
if (other is JsExternalBox) {
externrefEquals(ref, other.ref)
} else {
false
}
override fun hashCode(): Int =
externrefHashCode(ref)
}
//language=js
@JsFun("""
(() => {
const dataView = new DataView(new ArrayBuffer(8));
function numberHashCode(obj) {
if ((obj | 0) === obj) {
return obj | 0;
} else {
dataView.setFloat64(0, obj, true);
return (dataView.getInt32(0, true) * 31 | 0) + dataView.getInt32(4, true) | 0;
}
}
const hashCodes = new WeakMap();
function getObjectHashCode(obj) {
const res = hashCodes.get(obj);
if (res === undefined) {
const POW_2_32 = 4294967296;
const hash = (Math.random() * POW_2_32) | 0;
hashCodes.set(obj, hash);
return hash;
}
return res;
}
function getStringHashCode(str) {
var hash = 0;
for (var i = 0; i < str.length; i++) {
var code = str.charCodeAt(i);
hash = (hash * 31 + code) | 0;
}
return hash;
}
return (obj) => {
if (obj == null) {
return 0;
}
switch (typeof obj) {
case "object":
case "function":
return getObjectHashCode(obj);
case "number":
return numberHashCode(obj);
case "boolean":
return obj;
default:
return getStringHashCode(String(obj));
}
}
})()"""
)
private external fun externrefHashCode(ref: ExternalInterfaceType): Int
@JsFun("ref => String(ref)")
private external fun externrefToString(ref: ExternalInterfaceType): String
@JsFun("(lhs, rhs) => lhs === rhs")
private external fun externrefEquals(lhs: ExternalInterfaceType, rhs: ExternalInterfaceType): Boolean
@JsFun("(ref) => (typeof ref !== 'object' || ref === null) ? null : (externrefBoxes.get(ref) ?? null)")
private external fun getExternrefBoxOrNull(ref: ExternalInterfaceType): JsExternalBox?
@JsFun("(ref, box) => { if (typeof ref === 'object' && ref !== null) { externrefBoxes.set(ref, box); } }")
private external fun setExternrefBox(ref: ExternalInterfaceType, box: JsExternalBox)
@WasmNoOpCast
@Suppress("unused")
internal fun Any?.asWasmAnyref(): anyref =
implementedAsIntrinsic
@WasmNoOpCast
@Suppress("unused")
internal fun ExternalInterfaceType.externAsWasmAnyref(): anyref =
implementedAsIntrinsic
@WasmNoOpCast
@Suppress("unused")
internal fun Any?.asWasmExternRef(): ExternalInterfaceType =
implementedAsIntrinsic
@JsFun("(ref) => ref == null")
internal external fun isNullish(ref: ExternalInterfaceType): Boolean
internal fun externRefToAny(ref: ExternalInterfaceType): Any? {
// If ref is an instance of kotlin class -- return it cased to Any
val refAsAnyref = ref.externAsWasmAnyref()
if (wasm_ref_is_data(refAsAnyref)) {
val refAsDataRef = wasm_ref_as_data(refAsAnyref)
if (wasm_ref_test<Any>(refAsDataRef)) {
return wasm_ref_cast<Any>(refAsDataRef)
}
}
if (isNullish(ref))
return null
// If we already have a box -- return it,
// otherwise -- create a new box and remember it.
return getExternrefBoxOrNull(ref)
?: JsExternalBox(ref).also {
setExternrefBox(ref, it)
}
}
internal fun anyToExternRef(x: Any): ExternalInterfaceType {
return if (x is JsExternalBox)
x.ref
else
x.asWasmExternRef()
}
internal external fun importStringFromWasm(addr: Int): ExternalInterfaceType
@JsFun("x => x.length")
internal external fun stringLength(x: ExternalInterfaceType): Int
internal fun convertJsStringToKotlinString(x: ExternalInterfaceType): String {
val length = stringLength(x)
val addr = unsafeGetScratchRawMemory(INT_SIZE_BYTES + length * CHAR_SIZE_BYTES)
jsWriteStringIntoMemory(x, addr)
return kotlin.String(unsafeRawMemoryToCharArray(addr, length))
}
//language=js
@JsFun(
""" (str, addr) => {
const memory = new DataView(wasmInstance.exports.memory.buffer);
for (var i = 0; i < str.length; i++) {
memory.setInt16(addr + i * 2, str.charCodeAt(i), true);
}
}
"""
)
internal external fun jsWriteStringIntoMemory(str: ExternalInterfaceType, addr: Int)
internal fun kotlinToJsStringAdapter(x: String): ExternalInterfaceType {
return importStringFromWasm(exportString(x))
}
@JsFun("() => true")
internal external fun jsTrue(): ExternalInterfaceType
@JsFun("() => false")
internal external fun jsFalse(): ExternalInterfaceType
internal fun kotlinToJsBooleanAdapter(x: Boolean): ExternalInterfaceType =
if (x) jsTrue() else jsFalse()
internal fun kotlinToJsAnyAdapter(x: Any): ExternalInterfaceType =
anyToExternRef(x)
internal fun jsToKotlinAnyAdapter(x: ExternalInterfaceType): Any? =
externRefToAny(x)
internal fun jsToKotlinStringAdapter(x: ExternalInterfaceType): String =
convertJsStringToKotlinString(x)
internal fun jsToKotlinByteAdapter(x: Int): Byte = x.toByte()
internal fun jsToKotlinShortAdapter(x: Int): Short = x.toShort()
internal fun jsToKotlinCharAdapter(x: Int): Char = x.toChar()
@@ -9,6 +9,11 @@
package kotlin.wasm.internal
import kotlin.wasm.internal.reftypes.anyref
import kotlin.wasm.internal.reftypes.dataref
import kotlin.wasm.internal.reftypes.funcref
import kotlin.wasm.internal.reftypes.i31ref
@WasmOp(WasmOp.UNREACHABLE)
internal fun wasm_unreachable(): Nothing =
implementedAsIntrinsic
@@ -329,4 +334,25 @@ public external fun wasm_i64_clz(a: Long): Long
public external fun wasm_i64_popcnt(a: Long): Long
@WasmOp(WasmOp.I64_CTZ)
public external fun wasm_i64_ctz(a: Long): Long
public external fun wasm_i64_ctz(a: Long): Long
// Reference type operators
@WasmOp(WasmOp.REF_IS_FUNC)
internal external fun wasm_ref_is_func(x: anyref): Boolean
@WasmOp(WasmOp.REF_IS_DATA)
internal external fun wasm_ref_is_data(x: anyref): Boolean
@WasmOp(WasmOp.REF_IS_I31)
internal external fun wasm_ref_is_i31(x: anyref): Boolean
@WasmOp(WasmOp.REF_AS_FUNC)
internal external fun wasm_ref_as_func(x: anyref): funcref
@WasmOp(WasmOp.REF_AS_DATA)
internal external fun wasm_ref_as_data(x: anyref): dataref
@WasmOp(WasmOp.REF_AS_I31)
internal external fun wasm_ref_as_i31(x: anyref): i31ref
@@ -0,0 +1,26 @@
/*
* 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.
*/
@file:ExcludedFromCodegen
package kotlin.wasm.internal.reftypes
import kotlin.wasm.internal.*
// These interfaces correspond to Wasm GC reference types with the same name.
// They are not proper Kotlin interfaces and should be used with care.
//
// WARNING! Do not upcast to Any, nullable types, type parameters, etc.
// Do not use Any methods
// Do not use type operators `is`, `as`, etc.
// Do not use ==, ===
//
// Use dedicated intrinsics instead.
internal interface anyref
internal interface eqref : anyref
internal interface dataref : eqref
internal interface i31ref : eqref
internal interface funcref : anyref
@@ -216,6 +216,12 @@ internal annotation class WasmOp(val name: String) {
const val RETURN = "RETURN"
const val CALL = "CALL"
const val CALL_INDIRECT = "CALL_INDIRECT"
const val TRY = "TRY"
const val CATCH = "CATCH"
const val CATCH_ALL = "CATCH_ALL"
const val DELEGATE = "DELEGATE"
const val THROW = "THROW"
const val RETHROW = "RETHROW"
const val DROP = "DROP"
const val SELECT = "SELECT"
const val SELECT_TYPED = "SELECT_TYPED"
@@ -255,5 +261,19 @@ internal annotation class WasmOp(val name: String) {
const val REF_TEST = "REF_TEST"
const val REF_CAST = "REF_CAST"
const val BR_ON_CAST = "BR_ON_CAST"
const val BR_ON_CAST_FAIL = "BR_ON_CAST_FAIL"
const val REF_IS_FUNC = "REF_IS_FUNC"
const val REF_IS_DATA = "REF_IS_DATA"
const val REF_IS_I31 = "REF_IS_I31"
const val REF_AS_FUNC = "REF_AS_FUNC"
const val REF_AS_DATA = "REF_AS_DATA"
const val REF_AS_I31 = "REF_AS_I31"
const val BR_ON_FUNC = "BR_ON_FUNC"
const val BR_ON_DATA = "BR_ON_DATA"
const val BR_ON_I31 = "BR_ON_I31"
const val BR_ON_NON_FUNC = "BR_ON_NON_FUNC"
const val BR_ON_NON_DATA = "BR_ON_NON_DATA"
const val BR_ON_NON_I31 = "BR_ON_NON_I31"
const val GET_UNIT = "GET_UNIT"
}
}
@@ -13,4 +13,18 @@ package kotlin.js
@ExperimentalJsExport
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.FUNCTION)
public actual annotation class JsExport
public actual annotation class JsExport
/**
* Specifies JavaScript name for external and imported declarations
*/
@Retention(AnnotationRetention.BINARY)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
public actual annotation class JsName(actual val name: String)
@@ -368,6 +368,23 @@ enum class WasmOp(
BR_ON_CAST("br_on_cast", 0xFB_42, listOf(LABEL_IDX)),
BR_ON_CAST_FAIL("br_on_cast_fail", 0xfb43, listOf(LABEL_IDX)),
REF_IS_FUNC("ref.is_func", 0xfb50),
REF_IS_DATA("ref.is_data", 0xfb51),
REF_IS_I31("ref.is_i31", 0xfb52),
REF_AS_FUNC("ref.as_func", 0xfb58),
REF_AS_DATA("ref.as_data", 0xfb59),
REF_AS_I31("ref.as_i31", 0xfb5a),
BR_ON_FUNC("br_on_func", 0xfb60, listOf(LABEL_IDX)),
BR_ON_DATA("br_on_data", 0xfb61, listOf(LABEL_IDX)),
BR_ON_I31("br_on_i31", 0xfb62, listOf(LABEL_IDX)),
BR_ON_NON_FUNC("br_on_non_func", 0xfb63, listOf(LABEL_IDX)),
BR_ON_NON_DATA("br_on_non_data", 0xfb64, listOf(LABEL_IDX)),
BR_ON_NON_I31("br_on_non_i31", 0xfb65, listOf(LABEL_IDX)),
// Pseudo-instruction, just alias for a normal call. It's used to easily spot get_unit on the wasm level.
GET_UNIT("call", 0x10, FUNC_IDX)
;