diff --git a/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/declaration/FirWasmImportAnnotationChecker.kt b/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/declaration/FirWasmImportAnnotationChecker.kt index d5fad008d56..9dd58fb4aa2 100644 --- a/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/declaration/FirWasmImportAnnotationChecker.kt +++ b/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/declaration/FirWasmImportAnnotationChecker.kt @@ -72,5 +72,5 @@ private fun isTypeSupportedInWasmInterop( } // Primitive numbers and Boolean are supported - return type.isPrimitive && !type.isChar + return (type.isPrimitive && !type.isChar) || type.isUnsignedType } \ No newline at end of file diff --git a/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/declaration/FirWasmJsInteropTypesChecker.kt b/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/declaration/FirWasmJsInteropTypesChecker.kt index 3eb57380f35..bf46d939f3c 100644 --- a/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/declaration/FirWasmJsInteropTypesChecker.kt +++ b/compiler/fir/checkers/checkers.wasm/src/org/jetbrains/kotlin/fir/analysis/wasm/checkers/declaration/FirWasmJsInteropTypesChecker.kt @@ -122,7 +122,7 @@ private fun isTypeSupportedInJsInterop( val nonNullable = type.withNullability(ConeNullability.NOT_NULL, session.typeContext) - if (nonNullable.isPrimitive || nonNullable.isString) { + if (nonNullable.isPrimitive || nonNullable.isUnsignedType || nonNullable.isString) { return true } 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 9c925196a9d..0d8c632161f 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 @@ -133,6 +133,12 @@ class WasmSymbols( val voidType by lazy { voidClass.defaultType } private val consumeAnyIntoVoid = getInternalFunction("consumeAnyIntoVoid") + + val uByteType by lazy { getIrClass(FqName("kotlin.UByte")).defaultType } + val uShortType by lazy { getIrClass(FqName("kotlin.UShort")).defaultType } + val uIntType by lazy { getIrClass(FqName("kotlin.UInt")).defaultType } + val uLongType by lazy { getIrClass(FqName("kotlin.ULong")).defaultType } + private val consumePrimitiveIntoVoid = mapOf( context.irBuiltIns.booleanType to getInternalFunction("consumeBooleanIntoVoid"), context.irBuiltIns.byteType to getInternalFunction("consumeByteIntoVoid"), @@ -325,6 +331,11 @@ class WasmSymbols( val externRefToKotlinFloatAdapter = getInternalFunction("externRefToKotlinFloatAdapter") val externRefToKotlinDoubleAdapter = getInternalFunction("externRefToKotlinDoubleAdapter") + val externRefToKotlinUByteAdapter = getInternalFunction("externRefToKotlinUByteAdapter") + val externRefToKotlinUShortAdapter = getInternalFunction("externRefToKotlinUShortAdapter") + val externRefToKotlinUIntAdapter = getInternalFunction("externRefToKotlinUIntAdapter") + val externRefToKotlinULongAdapter = getInternalFunction("externRefToKotlinULongAdapter") + val kotlinIntToExternRefAdapter = getInternalFunction("kotlinIntToExternRefAdapter") val kotlinBooleanToExternRefAdapter = getInternalFunction("kotlinBooleanToExternRefAdapter") val kotlinLongToExternRefAdapter = getInternalFunction("kotlinLongToExternRefAdapter") @@ -333,6 +344,11 @@ class WasmSymbols( val kotlinByteToExternRefAdapter = getInternalFunction("kotlinByteToExternRefAdapter") val kotlinShortToExternRefAdapter = getInternalFunction("kotlinShortToExternRefAdapter") val kotlinCharToExternRefAdapter = getInternalFunction("kotlinCharToExternRefAdapter") + + val kotlinUByteToJsNumber = getInternalFunction("kotlinUByteToJsNumber") + val kotlinUShortToJsNumber = getInternalFunction("kotlinUShortToJsNumber") + val kotlinUIntToJsNumber = getInternalFunction("kotlinUIntToJsNumber") + val kotlinULongToJsBigInt = getInternalFunction("kotlinULongToJsBigInt") } inner class JsRelatedSymbols { 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 index a3fcd3ce7c4..8475034a6db 100644 --- 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 @@ -210,13 +210,19 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT val primitivesToExternRefAdapters: Map by lazy { mapOf( builtIns.byteType to adapters.kotlinByteToExternRefAdapter, + symbols.uByteType to adapters.kotlinUByteToJsNumber, builtIns.shortType to adapters.kotlinShortToExternRefAdapter, + symbols.uShortType to adapters.kotlinUShortToJsNumber, builtIns.charType to adapters.kotlinCharToExternRefAdapter, builtIns.intType to adapters.kotlinIntToExternRefAdapter, + symbols.uIntType to adapters.kotlinUIntToJsNumber, builtIns.longType to adapters.kotlinLongToExternRefAdapter, + symbols.uLongType to adapters.kotlinULongToJsBigInt, builtIns.floatType to adapters.kotlinFloatToExternRefAdapter, builtIns.doubleType to adapters.kotlinDoubleToExternRefAdapter, - ).mapValues { FunctionBasedAdapter(it.value.owner) } + ).mapValues { + FunctionBasedAdapter(it.value.owner) + } } private fun IrType.kotlinToJsAdapterIfNeeded(isReturn: Boolean): InteropTypeAdapter? { @@ -263,6 +269,11 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT builtIns.anyType -> return FunctionBasedAdapter(adapters.kotlinToJsAnyAdapter.owner) builtIns.numberType -> return FunctionBasedAdapter(adapters.numberToDoubleAdapter.owner) + symbols.uByteType -> return FunctionBasedAdapter(adapters.kotlinUByteToJsNumber.owner) + symbols.uShortType -> return FunctionBasedAdapter(adapters.kotlinUShortToJsNumber.owner) + symbols.uIntType -> return FunctionBasedAdapter(adapters.kotlinUIntToJsNumber.owner) + symbols.uLongType -> return FunctionBasedAdapter(adapters.kotlinULongToJsBigInt.owner) + builtIns.byteType, builtIns.shortType, builtIns.charType, @@ -320,16 +331,28 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT return SendKotlinObjectToJsAdapter(this) } - private fun createNullableAdapter(notNullType: IrType, isPrimitive: Boolean, valueAdapter: InteropTypeAdapter?): InteropTypeAdapter? { - return if (isPrimitive) { //nullable primitive should be checked and adapt to target type + private fun createNullableAdapter( + notNullType: IrType, + isPrimitiveOrUnsigned: Boolean, + valueAdapter: InteropTypeAdapter? + ): InteropTypeAdapter? { + return if (isPrimitiveOrUnsigned) { //nullable primitive should be checked and adapt to target type val externRefToPrimitiveAdapter = when (notNullType) { builtIns.floatType -> adapters.externRefToKotlinFloatAdapter.owner builtIns.doubleType -> adapters.externRefToKotlinDoubleAdapter.owner builtIns.longType -> adapters.externRefToKotlinLongAdapter.owner builtIns.booleanType -> adapters.externRefToKotlinBooleanAdapter.owner + + symbols.uByteType -> adapters.externRefToKotlinUByteAdapter.owner + symbols.uShortType -> adapters.externRefToKotlinUShortAdapter.owner + symbols.uIntType -> adapters.externRefToKotlinUIntAdapter.owner + symbols.uLongType -> adapters.externRefToKotlinULongAdapter.owner + else -> adapters.externRefToKotlinIntAdapter.owner } + val externalToPrimitiveAdapter = FunctionBasedAdapter(externRefToPrimitiveAdapter) + NullOrAdapter( adapter = valueAdapter?.let { CombineAdapter(it, externalToPrimitiveAdapter) } ?: externalToPrimitiveAdapter ) @@ -342,9 +365,13 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT } } - private fun createNotNullAdapter(notNullType: IrType, isPrimitive: Boolean, valueAdapter: InteropTypeAdapter?): InteropTypeAdapter? { + private fun createNotNullAdapter( + notNullType: IrType, + isPrimitiveOrUnsigned: Boolean, + valueAdapter: InteropTypeAdapter? + ): InteropTypeAdapter? { // !nullable primitive checked by wasm signature - if (isPrimitive) return valueAdapter + if (isPrimitiveOrUnsigned) return valueAdapter // !nullable reference should be null checked // notNullAdapter((undefined -> null)!!) @@ -367,12 +394,12 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT val notNullType = makeNotNull() val valueAdapter = notNullType.jsToKotlinAdapterIfNeededNotNullable(isReturn) - val isPrimitive = valueAdapter?.fromType?.isPrimitiveType() ?: notNullType.isPrimitiveType() + val isPrimitiveOrUnsigned = (valueAdapter?.fromType ?: notNullType).let { it.isPrimitiveType() || it.isUnsigned() } return if (isNullable()) - createNullableAdapter(notNullType, isPrimitive, valueAdapter) + createNullableAdapter(notNullType, isPrimitiveOrUnsigned, valueAdapter) else - createNotNullAdapter(notNullType, isPrimitive, valueAdapter) + createNotNullAdapter(notNullType, isPrimitiveOrUnsigned, valueAdapter) } private fun IrType.jsToKotlinAdapterIfNeededNotNullable(isReturn: Boolean): InteropTypeAdapter? { @@ -386,12 +413,16 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT builtIns.shortType -> return FunctionBasedAdapter(adapters.jsToKotlinShortAdapter.owner) builtIns.charType -> return FunctionBasedAdapter(adapters.jsToKotlinCharAdapter.owner) + symbols.uByteType, + symbols.uShortType, + symbols.uIntType, + symbols.uLongType, builtIns.booleanType, builtIns.intType, builtIns.longType, builtIns.floatType, builtIns.doubleType, - context.wasmSymbols.voidType -> + symbols.voidType -> return null } diff --git a/compiler/testData/codegen/boxWasmJsInterop/externalsWithUnsigned.kt b/compiler/testData/codegen/boxWasmJsInterop/externalsWithUnsigned.kt new file mode 100644 index 00000000000..08331c97fa1 --- /dev/null +++ b/compiler/testData/codegen/boxWasmJsInterop/externalsWithUnsigned.kt @@ -0,0 +1,150 @@ +// TARGET_BACKEND: WASM +// FILE: externals.js +function provideUByte() { return -1 } +function provideNullableUByte(nullable) { return nullable ? null : - 1 } + +function consumeUByte(x) { return x.toString() } +function consumeNullableUByte(x) { return x == null ? null : x.toString() } + +function provideUShort() { return -1 } +function provideNullableUShort(nullable) { return nullable ? null : - 1 } + +function consumeUShort(x) { return x.toString() } +function consumeNullableUShort(x) { return x == null ? null : x.toString() } + +function provideUInt() { return -1 } +function provideNullableUInt(nullable) { return nullable ? null : - 1 } + +function consumeUInt(x) { return x.toString() } +function consumeNullableUInt(x) { return x == null ? null : x.toString() } + +function provideULong() { return -1n } +function provideNullableULong(nullable) { return nullable ? null : - 1n } + +function consumeULong(x) { return x.toString() } +function consumeNullableULong(x) { return x == null ? null : x.toString() } + +function consumeUByteVararg(x) { return x.toString() } +function consumeNullableUByteVararg(x) { return x == null ? null : x.toString() } + +function consumeUShortVararg(x) { return x.toString() } +function consumeNullableUShortVararg(x) { return x == null ? null : x.toString() } + +function consumeUIntVararg(x) { return x.toString() } +function consumeNullableUIntVararg(x) { return x == null ? null : x.toString() } + +function consumeULongVararg(x) { return x.toString() } +function consumeNullableULongVararg(x) { return x == null ? null : x.toString() } + +// FILE: externals.kt +external fun provideUByte(): UByte + +external fun provideNullableUByte(nullable: Boolean): UByte? + +external fun consumeUByte(x: UByte): String + +external fun consumeNullableUByte(x: UByte?): String? + +external fun provideUShort(): UShort + +external fun provideNullableUShort(nullable: Boolean): UShort? + +external fun consumeUShort(x: UShort): String + +external fun consumeNullableUShort(x: UShort?): String? + +external fun provideUInt(): UInt + +external fun provideNullableUInt(nullable: Boolean): UInt? + +external fun consumeUInt(x: UInt): String + +external fun consumeNullableUInt(x: UInt?): String? + +external fun provideULong(): ULong + +external fun provideNullableULong(nullable: Boolean): ULong? + +external fun consumeULong(x: ULong): String + +external fun consumeNullableULong(x: ULong?): String? + +external fun consumeUByteVararg(vararg shorts: UByte): String + +external fun consumeNullableUByteVararg(vararg shorts: UByte?): String? + +external fun consumeUShortVararg(vararg shorts: UShort): String + +external fun consumeNullableUShortVararg(vararg shorts: UShort?): String? + +external fun consumeUIntVararg(vararg ints: UInt): String + +external fun consumeNullableUIntVararg(vararg ints: UInt?): String? + +external fun consumeULongVararg(vararg ints: ULong): String + +external fun consumeNullableULongVararg(vararg ints: ULong?): String? + +fun box(): String { + if (provideUByte() != UByte.MAX_VALUE) return "Fail 1" + if (provideNullableUByte(false) != UByte.MAX_VALUE) return "Fail 2" + if (provideNullableUByte(true) != null) return "Fail 3" + + if (provideUShort() != UShort.MAX_VALUE) return "Fail 4" + if (provideNullableUShort(false) != UShort.MAX_VALUE) return "Fail 5" + if (provideNullableUShort(true) != null) return "Fail 6" + + if (provideUInt() != UInt.MAX_VALUE) return "Fail 7" + if (provideNullableUInt(false) != UInt.MAX_VALUE) return "Fail 8" + if (provideNullableUInt(true) != null) return "Fail 9" + + if (provideULong() != ULong.MAX_VALUE) return "Fail 10" + if (provideNullableULong(false) != ULong.MAX_VALUE) return "Fail 11" + if (provideNullableULong(true) != null) return "Fail 12" + + if (consumeUByte(UByte.MAX_VALUE) != "255") return "Fail 13" + if (consumeNullableUByte(UByte.MAX_VALUE) != "255") return "Fail 14" + if (consumeNullableUByte(null) != null) return "Fail 15" + + if (consumeUShort(UShort.MAX_VALUE) != "65535") return "Fail 16" + if (consumeNullableUShort(UShort.MAX_VALUE) != "65535") return "Fail 17" + if (consumeNullableUShort(null) != null) return "Fail 18" + + if (consumeUInt(UInt.MAX_VALUE) != "4294967295") return "Fail 19" + if (consumeNullableUInt(UInt.MAX_VALUE) != "4294967295") return "Fail 20" + if (consumeNullableUInt(null) != null) return "Fail 21" + + if (consumeULong(ULong.MAX_VALUE) != "18446744073709551615") return "Fail 22" + if (consumeNullableULong(ULong.MAX_VALUE) != "18446744073709551615") return "Fail 23" + if (consumeNullableULong(null) != null) return "Fail 24" + + if (provideUShort() != UShort.MAX_VALUE) return "Fail 25" + if (provideNullableUShort(false) != UShort.MAX_VALUE) return "Fail 26" + if (provideNullableUShort(true) != null) return "Fail 27" + + if (provideUInt() != UInt.MAX_VALUE) return "Fail 28" + if (provideNullableUInt(false) != UInt.MAX_VALUE) return "Fail 29" + if (provideNullableUInt(true) != null) return "Fail 30" + + if (provideULong() != ULong.MAX_VALUE) return "Fail 31" + if (provideNullableULong(false) != ULong.MAX_VALUE) return "Fail 32" + if (provideNullableULong(true) != null) return "Fail 33" + + if (consumeUByteVararg(UByte.MAX_VALUE) != "255") return "Fail 34" + if (consumeNullableUByteVararg(UByte.MAX_VALUE) != "255") return "Fail 35" + if (consumeNullableUByteVararg(null) != null) return "Fail 36" + + if (consumeUShortVararg(UShort.MAX_VALUE) != "65535") return "Fail 37" + if (consumeNullableUShortVararg(UShort.MAX_VALUE) != "65535") return "Fail 38" + if (consumeNullableUShortVararg(null) != null) return "Fail 39" + + if (consumeUIntVararg(UInt.MAX_VALUE) != "4294967295") return "Fail 40" + if (consumeNullableUIntVararg(UInt.MAX_VALUE) != "4294967295") return "Fail 41" + if (consumeNullableUIntVararg(null) != null) return "Fail 42" + + if (consumeULongVararg(ULong.MAX_VALUE) != "18446744073709551615") return "Fail 43" + if (consumeNullableULongVararg(ULong.MAX_VALUE) != "18446744073709551615") return "Fail 44" + if (consumeNullableULongVararg(null) != null) return "Fail 45" + + return "OK" +} diff --git a/compiler/testData/codegen/boxWasmJsInterop/jsExport.kt b/compiler/testData/codegen/boxWasmJsInterop/jsExport.kt index 181660e0acc..2599b7eef2b 100644 --- a/compiler/testData/codegen/boxWasmJsInterop/jsExport.kt +++ b/compiler/testData/codegen/boxWasmJsInterop/jsExport.kt @@ -24,6 +24,54 @@ fun eiAsAny(ei: EI): JsReference = ei.toJsReference() @JsExport fun anyAsEI(any: JsReference): EI = any.get() as EI +@JsExport +fun provideUByte(): UByte = UByte.MAX_VALUE + +@JsExport +fun provideNullableUByte(nullable: Boolean): UByte? = if (nullable) null else UByte.MAX_VALUE + +@JsExport +fun consumeUByte(x: UByte) = x.toString() + +@JsExport +fun consumeNullableUByte(x: UByte?) = x?.toString() + +@JsExport +fun provideUShort(): UShort = UShort.MAX_VALUE + +@JsExport +fun provideNullableUShort(nullable: Boolean): UShort? = if (nullable) null else UShort.MAX_VALUE + +@JsExport +fun consumeUShort(x: UShort) = x.toString() + +@JsExport +fun consumeNullableUShort(x: UShort?) = x?.toString() + +@JsExport +fun provideUInt(): UInt = UInt.MAX_VALUE + +@JsExport +fun provideNullableUInt(nullable: Boolean): UInt? = if (nullable) null else UInt.MAX_VALUE + +@JsExport +fun consumeUInt(x: UInt) = x.toString() + +@JsExport +fun consumeNullableUInt(x: UInt?) = x?.toString() + +@JsExport +fun provideULong(): ULong = ULong.MAX_VALUE + +@JsExport +fun provideNullableULong(nullable: Boolean): ULong? = if (nullable) null else ULong.MAX_VALUE + +@JsExport +fun consumeULong(x: ULong) = x.toString() + +@JsExport +fun consumeNullableULong(x: ULong?) = x?.toString() + fun box(): String = "OK" // FILE: entry.mjs @@ -45,4 +93,138 @@ if (main.isEven(31) !== false || main.isEven(10) !== true) { if (main.anyAsEI(main.eiAsAny({x:10})).x !== 10) { throw "Fail 4"; -} \ No newline at end of file +} + +if (main.provideUByte() != 255) { + throw "Fail 5"; +} +if (main.provideUShort() != 65535) { + throw "Fail 6"; +} +if (main.provideUInt() != 4294967295) { + throw "Fail 7"; +} +if (main.provideULong() != 18446744073709551615n) { + throw "Fail 8"; +} + +if (main.provideNullableUByte(false) != 255) { + throw "Fail 9"; +} +if (main.provideNullableUByte(true) != null) { + throw "Fail 10"; +} +if (main.provideNullableUShort(false) != 65535) { + throw "Fail 11"; +} +if (main.provideNullableUShort(true) != null) { + throw "Fail 12"; +} +if (main.provideNullableUInt(false) != 4294967295) { + throw "Fail 13"; +} +if (main.provideNullableUInt(true) != null) { + throw "Fail 14"; +} +if (main.provideNullableULong(false) != 18446744073709551615n) { + throw "Fail 15"; +} +if (main.provideNullableULong(true) != null) { + throw "Fail 16"; +} + +if (main.consumeUByte(-1) != "255") { + throw "Fail 17"; +} +if (main.consumeNullableUByte(-1) != "255") { + throw "Fail 18"; +} +if (main.consumeNullableUByte(null) != null) { + throw "Fail 19"; +} + +if (main.consumeUShort(-1) != "65535") { + throw "Fail 20"; +} +if (main.consumeNullableUShort(-1) != "65535") { + throw "Fail 21"; +} +if (main.consumeNullableUShort(null) != null) { + throw "Fail 22"; +} + +if (main.consumeUInt(-1) != "4294967295") { + throw "Fail 23"; +} +if (main.consumeNullableUInt(-1) != "4294967295") { + throw "Fail 24"; +} +if (main.consumeNullableUInt(null) != null) { + throw "Fail 25"; +} + +if (main.consumeULong(-1n) != "18446744073709551615") { + throw "Fail 26"; +} +if (main.consumeNullableULong(-1n) != "18446744073709551615") { + throw "Fail 27"; +} +if (main.consumeNullableULong(null) != null) { + throw "Fail 28"; +} + +if (main.consumeUByte(255) != "255") { + throw "Fail 29"; +} +if (main.consumeNullableUByte(255) != "255") { + throw "Fail 30"; +} + +if (main.consumeUShort(65535) != "65535") { + throw "Fail 31"; +} +if (main.consumeNullableUShort(65535) != "65535") { + throw "Fail 32"; +} + +if (main.consumeUInt(4294967295) != "4294967295") { + throw "Fail 33"; +} +if (main.consumeNullableUInt(4294967295) != "4294967295") { + throw "Fail 34"; +} + +if (main.consumeULong(18446744073709551615n) != "18446744073709551615") { + throw "Fail 35"; +} +if (main.consumeNullableULong(18446744073709551615n) != "18446744073709551615") { + throw "Fail 36"; +} + +if (main.consumeUByte(256) != "0") { + throw "Fail 37"; +} +if (main.consumeNullableUByte(256) != "0") { + throw "Fail 38"; +} + +if (main.consumeUShort(65536) != "0") { + throw "Fail 39"; +} +if (main.consumeNullableUShort(65536) != "0") { + throw "Fail 40"; +} + +if (main.consumeUInt(4294967296) != "0") { + throw "Fail 41"; +} +if (main.consumeNullableUInt(4294967296) != "0") { + throw "Fail 42"; +} + +if (main.consumeULong(18446744073709551616n) != "0") { + throw "Fail 43"; +} +if (main.consumeNullableULong(18446744073709551616n) != "0") { + throw "Fail 44"; +} diff --git a/compiler/testData/codegen/boxWasmJsInterop/wasmExport.kt b/compiler/testData/codegen/boxWasmJsInterop/wasmExport.kt index 3bf7db3b0bf..3357359a9dc 100644 --- a/compiler/testData/codegen/boxWasmJsInterop/wasmExport.kt +++ b/compiler/testData/codegen/boxWasmJsInterop/wasmExport.kt @@ -2,18 +2,37 @@ import kotlin.wasm.WasmExport -@WasmExport -fun exportDefaultName(): Boolean = true - -fun checkDefaultName(): Boolean = js("typeof wasmExports.exportDefaultName() !== 'object'") - @WasmExport("exportOverriddenName") fun exportWithName(): Boolean = true +@WasmExport +fun exportDefaultName(): Boolean = true + +@WasmExport +fun provideUByte(): UByte = UByte.MAX_VALUE + +@WasmExport +fun provideUShort(): UShort = UShort.MAX_VALUE + +@WasmExport +fun provideUInt(): UInt = UInt.MAX_VALUE + +@WasmExport +fun provideULong(): ULong = ULong.MAX_VALUE + +fun checkDefaultName(): Boolean = js("typeof wasmExports.exportDefaultName() !== 'object'") fun checkOverriddenName(): Boolean = js("typeof wasmExports.exportOverriddenName() !== 'object'") +fun checkProvideUByte(): Boolean = js("wasmExports.provideUByte() === -1") +fun checkProvideUShort(): Boolean = js("wasmExports.provideUShort() === -1") +fun checkProvideUInt(): Boolean = js("wasmExports.provideUInt() === -1") +fun checkProvideULong(): Boolean = js("wasmExports.provideULong() === -1n") fun box(): String { if (!checkDefaultName()) return "checkDefaultName fail" if (!checkOverriddenName()) return "checkOverriddenName fail" + if (!checkProvideUByte()) return "checkProvideUByte fail" + if (!checkProvideUShort()) return "checkProvideUShort fail" + if (!checkProvideUInt()) return "checkProvideUInt fail" + if (!checkProvideULong()) return "checkProvideULong fail" return "OK" } \ No newline at end of file diff --git a/compiler/testData/codegen/boxWasmJsInterop/wasmImport.kt b/compiler/testData/codegen/boxWasmJsInterop/wasmImport.kt index 9a802505109..68d5e295a62 100644 --- a/compiler/testData/codegen/boxWasmJsInterop/wasmImport.kt +++ b/compiler/testData/codegen/boxWasmJsInterop/wasmImport.kt @@ -20,6 +20,16 @@ export { sub as "" } export { sub as "\n \r \t" } export default sub; +// FILE: 3.mjs + +export function provideUByte() { return -1 } + +export function provideUShort() { return -1 } + +export function provideUInt() { return -1 } + +export function provideULong() { return -1n } + // FILE: wasmImport.kt import kotlin.wasm.WasmImport @@ -50,6 +60,18 @@ external fun sub5(x: Float, y: Float): Float @WasmImport("./2.mjs", "default") external fun sub6(x: Float, y: Float): Float +@WasmImport("./3.mjs", "provideUByte") +external fun provideUByte(): UByte + +@WasmImport("./3.mjs", "provideUShort") +external fun provideUShort(): UShort + +@WasmImport("./3.mjs", "provideUInt") +external fun provideUInt(): UInt + +@WasmImport("./3.mjs", "provideULong") +external fun provideULong(): ULong + fun box(): String { if (addImportRenamed(5, 6) != 11) return "Fail1" if (add(5, 6) != 11) return "Fail1" @@ -62,5 +84,10 @@ fun box(): String { if (sub5(5f, 6f) != -1f) return "Fail6" if (sub6(5f, 6f) != -1f) return "Fail7" + if (provideUByte() != UByte.MAX_VALUE) return "Fail9" + if (provideUShort() != UShort.MAX_VALUE) return "Fail10" + if (provideUInt() != UInt.MAX_VALUE) return "Fail11" + if (provideULong() != ULong.MAX_VALUE) return "Fail12" + return "OK" } \ No newline at end of file diff --git a/compiler/testData/diagnostics/wasmTests/jsInterop/jsExport.kt b/compiler/testData/diagnostics/wasmTests/jsInterop/jsExport.kt index 68a33d49475..e866c21d32a 100644 --- a/compiler/testData/diagnostics/wasmTests/jsInterop/jsExport.kt +++ b/compiler/testData/diagnostics/wasmTests/jsInterop/jsExport.kt @@ -24,3 +24,9 @@ class C2 @JsExport var p2: Int = 1 + +@JsExport +fun fooUnsigned1(): UInt = 42u + +@JsExport +fun fooUnsigned2(): UByte = 42u diff --git a/compiler/testData/diagnostics/wasmTests/wasmInterop/wasmExport.kt b/compiler/testData/diagnostics/wasmTests/wasmInterop/wasmExport.kt index 82b0b9db580..bc9856e19ba 100644 --- a/compiler/testData/diagnostics/wasmTests/wasmInterop/wasmExport.kt +++ b/compiler/testData/diagnostics/wasmTests/wasmInterop/wasmExport.kt @@ -58,3 +58,9 @@ fun fooDeafultAndVararg( a: Int = definedExternally, vararg b: Int ): Unit { b.toString() } + +@WasmExport("a") +fun fooUnsigned1(): UInt = 42u + +@WasmExport() +fun fooUnsigned2(): UByte = 42u diff --git a/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java b/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java index d43cacccaed..14bdca083a7 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java +++ b/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java @@ -956,6 +956,10 @@ public abstract class KotlinBuiltIns { return type != null && isNotNullConstructedFromGivenClass(type, FqNames.string); } + public static boolean isUnsignedNumber(@Nullable KotlinType type) { + return type != null && (isUByte(type) || isUShort(type) || isUInt(type) || isULong(type)); + } + public static boolean isCharSequenceOrNullableCharSequence(@Nullable KotlinType type) { return type != null && isConstructedFromGivenClass(type, FqNames.charSequence); } diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt index aaab915281a..bc74e7ba978 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt @@ -27,7 +27,6 @@ object JsPlatformConfigurator : PlatformConfiguratorBase( JsRuntimeAnnotationChecker, JsDynamicDeclarationChecker, JsExportAnnotationChecker, - JsExportDeclarationChecker ), additionalCallCheckers = listOf( JsModuleCallChecker, @@ -52,6 +51,7 @@ object JsPlatformConfigurator : PlatformConfiguratorBase( container.useInstance(ExtensionFunctionToExternalIsInlinable) container.useInstance(JsQualifierChecker) container.useInstance(JsNativeDiagnosticSuppressor) + container.useInstance(JsExportDeclarationChecker(includeUnsignedNumbers = false)) } override fun configureModuleDependentCheckers(container: StorageComponentContainer) { diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt index 188208e46ce..6d7d5caf8ac 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsExportDeclarationChecker.kt @@ -33,7 +33,7 @@ import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.isDynamic import org.jetbrains.kotlin.types.typeUtil.* -object JsExportDeclarationChecker : DeclarationChecker { +class JsExportDeclarationChecker(private val includeUnsignedNumbers: Boolean) : DeclarationChecker { override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { val trace = context.trace val bindingContext = trace.bindingContext @@ -190,8 +190,9 @@ object JsExportDeclarationChecker : DeclarationChecker { KotlinBuiltIns.isString(nonNullable) || (nonNullable.isPrimitiveNumberOrNullableType() && !nonNullable.isLong()) || nonNullable.isNothingOrNullableNothing() || - KotlinBuiltIns.isArray(this) || - KotlinBuiltIns.isPrimitiveArray(this) + KotlinBuiltIns.isArray(nonNullable) || + KotlinBuiltIns.isPrimitiveArray(nonNullable) || + (includeUnsignedNumbers && KotlinBuiltIns.isUnsignedNumber(nonNullable)) if (isPrimitiveExportableType) return true diff --git a/libraries/stdlib/wasm/js/internal/ExternalWrapper.kt b/libraries/stdlib/wasm/js/internal/ExternalWrapper.kt index e703a8f3467..85002f18f30 100644 --- a/libraries/stdlib/wasm/js/internal/ExternalWrapper.kt +++ b/libraries/stdlib/wasm/js/internal/ExternalWrapper.kt @@ -91,11 +91,23 @@ private external fun externrefHashCode(ref: ExternalInterfaceType): Int private fun externrefToString(ref: ExternalInterfaceType): String = js("String(ref)") +private fun externrefToUByte(ref: ExternalInterfaceType): UByte = + js("Number(ref)") + +private fun externrefToUShort(ref: ExternalInterfaceType): UShort = + js("Number(ref)") + +private fun externrefToUInt(ref: ExternalInterfaceType): UInt = + js("Number(ref)") + +private fun externrefToULong(ref: ExternalInterfaceType): ULong = + js("BigInt(ref)") + private fun externrefToInt(ref: ExternalInterfaceType): Int = js("Number(ref)") private fun externrefToLong(ref: ExternalInterfaceType): Long = - js("Number(ref)") + js("BigInt(ref)") private fun externrefToBoolean(ref: ExternalInterfaceType): Boolean = js("Boolean(ref)") @@ -314,6 +326,18 @@ internal fun jsToKotlinByteAdapter(x: Int): Byte = x.toByte() internal fun jsToKotlinShortAdapter(x: Int): Short = x.toShort() internal fun jsToKotlinCharAdapter(x: Int): Char = x.toChar() +internal fun externRefToKotlinUByteAdapter(x: ExternalInterfaceType): UByte = + externrefToUByte(x) + +internal fun externRefToKotlinUShortAdapter(x: ExternalInterfaceType): UShort = + externrefToUShort(x) + +internal fun externRefToKotlinUIntAdapter(x: ExternalInterfaceType): UInt = + externrefToUInt(x) + +internal fun externRefToKotlinULongAdapter(x: ExternalInterfaceType): ULong = + externrefToULong(x) + internal fun externRefToKotlinIntAdapter(x: ExternalInterfaceType): Int = externrefToInt(x) @@ -335,6 +359,30 @@ internal fun kotlinIntToExternRefAdapter(x: Int): JsNumber = internal fun kotlinBooleanToExternRefAdapter(x: Boolean): JsBoolean = if (x) jsTrue else jsFalse +private fun kotlinUByteToJsNumberUnsafe(x: Int): JsNumber = + js("x & 0xFF") + +private fun kotlinUShortToJsNumberUnsafe(x: Int): JsNumber = + js("x & 0xFFFF") + +private fun kotlinUIntToJsNumberUnsafe(x: Int): JsNumber = + js("x >>> 0") + +private fun kotlinULongToJsBigIntUnsafe(x: Long): ExternalInterfaceType = + js("x & 0xFFFFFFFFFFFFFFFFn") + +internal fun kotlinUByteToJsNumber(x: UByte): JsNumber = + kotlinUByteToJsNumberUnsafe(x.toInt()) + +internal fun kotlinUShortToJsNumber(x: UShort): JsNumber = + kotlinUShortToJsNumberUnsafe(x.toInt()) + +internal fun kotlinUIntToJsNumber(x: UInt): JsNumber = + kotlinUIntToJsNumberUnsafe(x.toInt()) + +internal fun kotlinULongToJsBigInt(x: ULong): ExternalInterfaceType = + kotlinULongToJsBigIntUnsafe(x.toLong()) + internal fun kotlinLongToExternRefAdapter(x: Long): ExternalInterfaceType = longToExternref(x) diff --git a/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/WasmPlatformConfigurator.kt b/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/WasmPlatformConfigurator.kt index 213bfa05429..0750011a9a5 100644 --- a/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/WasmPlatformConfigurator.kt +++ b/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/WasmPlatformConfigurator.kt @@ -26,7 +26,6 @@ object WasmJsPlatformConfigurator : PlatformConfiguratorBase( JsExternalChecker, WasmExternalInheritanceChecker, JsRuntimeAnnotationChecker, JsExportAnnotationChecker, - JsExportDeclarationChecker, WasmExternalDeclarationChecker, WasmImportAnnotationChecker, WasmJsFunAnnotationChecker, @@ -51,6 +50,7 @@ object WasmJsPlatformConfigurator : PlatformConfiguratorBase( container.useInstance(ExtensionFunctionToExternalIsInlinable) container.useInstance(JsQualifierChecker) container.useInstance(WasmDiagnosticSuppressor) + container.useInstance(JsExportDeclarationChecker(includeUnsignedNumbers = true)) } override fun configureModuleDependentCheckers(container: StorageComponentContainer) { diff --git a/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/diagnostics/WasmImportAnnotationChecker.kt b/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/diagnostics/WasmImportAnnotationChecker.kt index e96d3b768fa..6c918d332de 100644 --- a/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/diagnostics/WasmImportAnnotationChecker.kt +++ b/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/diagnostics/WasmImportAnnotationChecker.kt @@ -31,6 +31,7 @@ import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.isBoolean import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberType import org.jetbrains.kotlin.types.typeUtil.isUnit +import org.jetbrains.kotlin.types.typeUtil.isUnsignedNumberType object WasmImportAnnotationChecker : DeclarationChecker { private val wasmImportFqName = FqName("kotlin.wasm.WasmImport") @@ -54,7 +55,7 @@ object WasmImportAnnotationChecker : DeclarationChecker { } private fun isParameterTypeSupported(type: KotlinType): Boolean = - type.isPrimitiveNumberType() || type.isBoolean() + type.isPrimitiveNumberType() || type.isUnsignedNumberType() || type.isBoolean() private fun isReturnTypeSupported(type: KotlinType): Boolean = isParameterTypeSupported(type) || type.isUnit() diff --git a/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/diagnostics/WasmJsInteropTypesChecker.kt b/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/diagnostics/WasmJsInteropTypesChecker.kt index 12b13a27213..357b5932916 100644 --- a/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/diagnostics/WasmJsInteropTypesChecker.kt +++ b/wasm/wasm.frontend/src/org/jetbrains/kotlin/wasm/resolve/diagnostics/WasmJsInteropTypesChecker.kt @@ -113,6 +113,7 @@ private fun isTypeSupportedInJsInterop( val nonNullable = type.makeNotNullable() if ( KotlinBuiltIns.isPrimitiveType(nonNullable) || + KotlinBuiltIns.isUnsignedNumber(nonNullable) || KotlinBuiltIns.isString(nonNullable) ) { return true diff --git a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/FirWasmCodegenWasmJsInteropTestGenerated.java b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/FirWasmCodegenWasmJsInteropTestGenerated.java index 323118c10d1..71393acfde8 100644 --- a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/FirWasmCodegenWasmJsInteropTestGenerated.java +++ b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/FirWasmCodegenWasmJsInteropTestGenerated.java @@ -49,6 +49,12 @@ public class FirWasmCodegenWasmJsInteropTestGenerated extends AbstractFirWasmCod runTest("compiler/testData/codegen/boxWasmJsInterop/externals.kt"); } + @Test + @TestMetadata("externalsWithUnsigned.kt") + public void testExternalsWithUnsigned() throws Exception { + runTest("compiler/testData/codegen/boxWasmJsInterop/externalsWithUnsigned.kt"); + } + @Test @TestMetadata("functionTypes.kt") public void testFunctionTypes() throws Exception { diff --git a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmCodegenWasmJsInteropTestGenerated.java b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmCodegenWasmJsInteropTestGenerated.java index 1b292b442a5..a81a330f449 100644 --- a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmCodegenWasmJsInteropTestGenerated.java +++ b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmCodegenWasmJsInteropTestGenerated.java @@ -49,6 +49,12 @@ public class K1WasmCodegenWasmJsInteropTestGenerated extends AbstractK1WasmCodeg runTest("compiler/testData/codegen/boxWasmJsInterop/externals.kt"); } + @Test + @TestMetadata("externalsWithUnsigned.kt") + public void testExternalsWithUnsigned() throws Exception { + runTest("compiler/testData/codegen/boxWasmJsInterop/externalsWithUnsigned.kt"); + } + @Test @TestMetadata("functionTypes.kt") public void testFunctionTypes() throws Exception {