From 4fc461a2ffeb2d3030b1a35f61d5811a9b8e7e93 Mon Sep 17 00:00:00 2001 From: Svyatoslav Kuzmich Date: Fri, 15 Oct 2021 23:04:22 +0300 Subject: [PATCH] [Wasm] Imporove external interface support * Support boxing/unboxing when casting to Any * Support ===, equals, hashCode, toString * Support adapting String in interop boundary --- .../kotlin_test_wasm_js_1_6_255_SNAPSHOT.xml | 4 +- .../kotlin/backend/wasm/WasmBackendContext.kt | 1 - .../kotlin/backend/wasm/WasmLoweringPhases.kt | 27 +- .../kotlin/backend/wasm/WasmSymbols.kt | 26 +- .../jetbrains/kotlin/backend/wasm/compiler.kt | 8 +- .../backend/wasm/ir2wasm/BodyGenerator.kt | 30 -- .../wasm/ir2wasm/DeclarationGenerator.kt | 31 +- .../backend/wasm/ir2wasm/TypeTransformer.kt | 21 +- .../wasm/ir2wasm/WasmBaseCodegenContext.kt | 1 - .../ir2wasm/WasmModuleCodegenContextImpl.kt | 7 - .../ExplicitlyCastExternalTypesLowering.kt | 29 ++ .../wasm/lower/JsInteropFunctionsLowering.kt | 308 ++++++++++++++++++ .../wasm/lower/WasmTypeOperatorLowering.kt | 25 +- .../markAdditionalExportedDeclarations.kt | 27 ++ .../kotlin/ir/backend/js/JsMapping.kt | 4 + .../boxWasmJsInterop/externalTypeOperators.kt | 231 +++++++++++++ .../codegen/boxWasmJsInterop/jsExport.kt | 26 ++ .../boxWasmJsInterop/nullableExternRefs.kt | 65 ++++ .../codegen/boxWasmJsInterop/types.kt | 112 +++++++ .../kotlin/js/test/BasicWasmBoxTest.kt | 2 +- .../abstractClassesForGeneratedIrTests.kt | 6 + .../abstractClassesForGeneratedTests.kt | 5 - ...IrCodegenWasmJsInteropJsTestGenerated.java | 12 +- ...CodegenWasmJsInteropWasmTestGenerated.java | 15 + .../kotlin/wasm/internal/ExternalWrapper.kt | 191 +++++++++++ .../kotlin/wasm/internal/WasmInstructions.kt | 28 +- .../internal/reftypes/WasmReferenceTypes.kt | 26 ++ .../stdlib/wasm/src/generated/_WasmOp.kt | 20 ++ .../stdlib/wasm/src/kotlin/js/annotations.kt | 16 +- .../org/jetbrains/kotlin/wasm/ir/Operators.kt | 17 + 30 files changed, 1240 insertions(+), 81 deletions(-) create mode 100644 compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/ExplicitlyCastExternalTypesLowering.kt create mode 100644 compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/JsInteropFunctionsLowering.kt create mode 100644 compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/markAdditionalExportedDeclarations.kt create mode 100644 compiler/testData/codegen/boxWasmJsInterop/externalTypeOperators.kt create mode 100644 compiler/testData/codegen/boxWasmJsInterop/nullableExternRefs.kt create mode 100644 compiler/testData/codegen/boxWasmJsInterop/types.kt rename js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/{ => ir}/semantics/IrCodegenWasmJsInteropJsTestGenerated.java (80%) create mode 100644 libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt create mode 100644 libraries/stdlib/wasm/internal/kotlin/wasm/internal/reftypes/WasmReferenceTypes.kt diff --git a/.idea/artifacts/kotlin_test_wasm_js_1_6_255_SNAPSHOT.xml b/.idea/artifacts/kotlin_test_wasm_js_1_6_255_SNAPSHOT.xml index 6ec66790f30..bf25edaae1f 100644 --- a/.idea/artifacts/kotlin_test_wasm_js_1_6_255_SNAPSHOT.xml +++ b/.idea/artifacts/kotlin_test_wasm_js_1_6_255_SNAPSHOT.xml @@ -1,6 +1,8 @@ $PROJECT_DIR$/libraries/kotlin.test/wasm/build/libs - + + + \ No newline at end of file diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt index 5595d89f7a9..e0090ea7e9e 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt @@ -35,7 +35,6 @@ class WasmBackendContext( override val irBuiltIns: IrBuiltIns, symbolTable: SymbolTable, irModuleFragment: IrModuleFragment, - val additionalExportedDeclarations: Set, override val configuration: CompilerConfiguration, ) : JsCommonBackendContext { override val builtIns = module.builtIns diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt index b79cbe3090f..321532cf7c7 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt @@ -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 diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt index bb99b2d2b98..7c14b65c829 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt @@ -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 diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt index 50500b5e7cb..4afaa8f3a6b 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt @@ -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; diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt index 21eb87599f7..60d12456cfe 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt @@ -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) } diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt index c6322b6e286..10fb96205d0 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt @@ -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 { return listOfNotNull(implicitThis, dispatchReceiverParameter, extensionReceiverParameter) + valueParameters } -fun IrFunction.isExported(context: WasmBackendContext): Boolean = - fqNameWhenAvailable in context.additionalExportedDeclarations || isJsExport() \ No newline at end of file +fun IrFunction.isExported(): Boolean = + isJsExport() \ No newline at end of file diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/TypeTransformer.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/TypeTransformer.kt index 4e398a52fce..e09ea0b8800 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/TypeTransformer.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/TypeTransformer.kt @@ -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? diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmBaseCodegenContext.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmBaseCodegenContext.kt index c8ee0e470bb..7c1678b736c 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmBaseCodegenContext.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmBaseCodegenContext.kt @@ -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? diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt index faf591dd026..98a125089ac 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt @@ -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() } } diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/ExplicitlyCastExternalTypesLowering.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/ExplicitlyCastExternalTypesLowering.kt new file mode 100644 index 00000000000..190a86c24e6 --- /dev/null +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/ExplicitlyCastExternalTypesLowering.kt @@ -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 + } +} diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/JsInteropFunctionsLowering.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/JsInteropFunctionsLowering.kt new file mode 100644 index 00000000000..2c75b43f21a --- /dev/null +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/JsInteropFunctionsLowering.kt @@ -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? { + 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? { + 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 { } + * + * -> + * + * @JsExport + * @JsName("foo") + * fun foo__JsExportAdapter(x: JsType): JsType = + * adaptResult(foo(adaptParameter(x))); + * + * fun foo(x: KotlinType): KotlinType { } + */ + fun transformExportFunction(function: IrSimpleFunction): List? { + 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, + 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 + } + } + }) + } +} \ No newline at end of file diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmTypeOperatorLowering.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmTypeOperatorLowering.kt index a7b56793175..456ac17fec1 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmTypeOperatorLowering.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmTypeOperatorLowering.kt @@ -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()) { diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/markAdditionalExportedDeclarations.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/markAdditionalExportedDeclarations.kt new file mode 100644 index 00000000000..689123c95a3 --- /dev/null +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/markAdditionalExportedDeclarations.kt @@ -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) { + 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()) + } + } +} \ No newline at end of file diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt index a93360987b1..48db281457e 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt @@ -40,6 +40,10 @@ class JsMapping(val state: JsMappingState) : DefaultMapping(state) { state.newDeclarationToDeclarationMapping() val suspendArityStore = state.newDeclarationToDeclarationCollectionMapping>() + + // Wasm mappings + val wasmJsInteropFunctionToWrapper = + state.newDeclarationToDeclarationMapping() } diff --git a/compiler/testData/codegen/boxWasmJsInterop/externalTypeOperators.kt b/compiler/testData/codegen/boxWasmJsInterop/externalTypeOperators.kt new file mode 100644 index 00000000000..ac85fe9f14b --- /dev/null +++ b/compiler/testData/codegen/boxWasmJsInterop/externalTypeOperators.kt @@ -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 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(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, 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" +} \ No newline at end of file diff --git a/compiler/testData/codegen/boxWasmJsInterop/jsExport.kt b/compiler/testData/codegen/boxWasmJsInterop/jsExport.kt index e381097831c..6b48f67016a 100644 --- a/compiler/testData/codegen/boxWasmJsInterop/jsExport.kt +++ b/compiler/testData/codegen/boxWasmJsInterop/jsExport.kt @@ -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"; } \ No newline at end of file diff --git a/compiler/testData/codegen/boxWasmJsInterop/nullableExternRefs.kt b/compiler/testData/codegen/boxWasmJsInterop/nullableExternRefs.kt new file mode 100644 index 00000000000..cf50522e8b9 --- /dev/null +++ b/compiler/testData/codegen/boxWasmJsInterop/nullableExternRefs.kt @@ -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" +} \ No newline at end of file diff --git a/compiler/testData/codegen/boxWasmJsInterop/types.kt b/compiler/testData/codegen/boxWasmJsInterop/types.kt new file mode 100644 index 00000000000..f49123b70a7 --- /dev/null +++ b/compiler/testData/codegen/boxWasmJsInterop/types.kt @@ -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 ?? "", y ?? "") +} + +// -- 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" +} \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt index 7cf6f1ebfc2..c5177f72c93 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt @@ -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() diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/abstractClassesForGeneratedIrTests.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/abstractClassesForGeneratedIrTests.kt index 5d1d1f3cd54..a7bf227a984 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/abstractClassesForGeneratedIrTests.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/abstractClassesForGeneratedIrTests.kt @@ -23,3 +23,9 @@ abstract class AbstractIrJsCodegenInlineTest : BasicIrBoxTest( "compiler/testData/codegen/boxInline/", "codegen/irBoxInline/" ) + + +abstract class AbstractIrCodegenWasmJsInteropJsTest : BasicIrBoxTest( + "compiler/testData/codegen/wasmJsInterop", + "codegen/wasmJsInteropJs" +) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt index 2ffb64a28eb..664635f6294 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt @@ -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/", diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/IrCodegenWasmJsInteropJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrCodegenWasmJsInteropJsTestGenerated.java similarity index 80% rename from js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/IrCodegenWasmJsInteropJsTestGenerated.java rename to js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrCodegenWasmJsInteropJsTestGenerated.java index a5b348ed595..d92027949f9 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/IrCodegenWasmJsInteropJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrCodegenWasmJsInteropJsTestGenerated.java @@ -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"); + } } diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/wasm/semantics/IrCodegenWasmJsInteropWasmTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/wasm/semantics/IrCodegenWasmJsInteropWasmTestGenerated.java index 31f3817edd2..2370e26c62c 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/wasm/semantics/IrCodegenWasmJsInteropWasmTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/wasm/semantics/IrCodegenWasmJsInteropWasmTestGenerated.java @@ -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"); + } } diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.kt new file mode 100644 index 00000000000..e77472a4b2b --- /dev/null +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/ExternalWrapper.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(refAsDataRef)) { + return wasm_ref_cast(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() diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmInstructions.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmInstructions.kt index 692474f9602..1ba074f4d8b 100644 --- a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmInstructions.kt +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/WasmInstructions.kt @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/reftypes/WasmReferenceTypes.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/reftypes/WasmReferenceTypes.kt new file mode 100644 index 00000000000..bbf9d671d51 --- /dev/null +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/reftypes/WasmReferenceTypes.kt @@ -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 \ No newline at end of file diff --git a/libraries/stdlib/wasm/src/generated/_WasmOp.kt b/libraries/stdlib/wasm/src/generated/_WasmOp.kt index 87e2f12b01a..82d188a1894 100644 --- a/libraries/stdlib/wasm/src/generated/_WasmOp.kt +++ b/libraries/stdlib/wasm/src/generated/_WasmOp.kt @@ -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" } } diff --git a/libraries/stdlib/wasm/src/kotlin/js/annotations.kt b/libraries/stdlib/wasm/src/kotlin/js/annotations.kt index bb890f25b40..e075105e945 100644 --- a/libraries/stdlib/wasm/src/kotlin/js/annotations.kt +++ b/libraries/stdlib/wasm/src/kotlin/js/annotations.kt @@ -13,4 +13,18 @@ package kotlin.js @ExperimentalJsExport @Retention(AnnotationRetention.BINARY) @Target(AnnotationTarget.FUNCTION) -public actual annotation class JsExport \ No newline at end of file +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) \ No newline at end of file diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt index f63f43edbe9..18965959c54 100644 --- a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt +++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt @@ -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) ;