| /* |
| * Copyright 2010-2023 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:Suppress("UNUSED_PARAMETER") // TODO: Remove after bootstrap update |
| |
| package kotlin.wasm.internal |
| |
| import kotlin.wasm.internal.reftypes.anyref |
| import kotlin.wasm.unsafe.withScopedMemoryAllocator |
| import kotlin.wasm.unsafe.UnsafeWasmMemoryApi |
| |
| internal typealias ExternalInterfaceType = JsAny |
| |
| internal class JsExternalBox @WasmPrimitiveConstructor constructor(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 { |
| var hashCode = _hashCode |
| if (hashCode != 0) return hashCode |
| hashCode = externrefHashCode(ref) |
| _hashCode = hashCode |
| return hashCode |
| } |
| } |
| |
| @Suppress("DEPRECATION") |
| //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 ? 1231 : 1237; |
| default: |
| return getStringHashCode(String(obj)); |
| } |
| } |
| })()""" |
| ) |
| 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("BigInt(ref)") |
| |
| private fun externrefToBoolean(ref: ExternalInterfaceType): Boolean = |
| js("Boolean(ref)") |
| |
| private fun externrefToFloat(ref: ExternalInterfaceType): Float = |
| js("Number(ref)") |
| |
| private fun externrefToDouble(ref: ExternalInterfaceType): Double = |
| js("Number(ref)") |
| |
| private fun intToExternref(x: Int): JsNumber = |
| js("x") |
| |
| private fun longToExternref(x: Long): JsBigInt = |
| js("x") |
| |
| private fun booleanToExternref(x: Boolean): JsBoolean = |
| js("x") |
| |
| private fun floatToExternref(x: Float): JsNumber = |
| js("x") |
| |
| private fun doubleToExternref(x: Double): JsNumber = |
| js("x") |
| |
| private fun externrefEquals(lhs: ExternalInterfaceType, rhs: ExternalInterfaceType): Boolean = |
| js("lhs === rhs") |
| |
| |
| @WasmNoOpCast |
| @Suppress("unused") |
| private fun Any?.asWasmAnyref(): anyref = |
| implementedAsIntrinsic |
| |
| @WasmOp(WasmOp.EXTERN_INTERNALIZE) |
| private fun ExternalInterfaceType.externAsWasmAnyref(): anyref = |
| implementedAsIntrinsic |
| |
| @WasmOp(WasmOp.EXTERN_EXTERNALIZE) |
| private fun Any.asWasmExternRef(): ExternalInterfaceType = |
| implementedAsIntrinsic |
| |
| internal fun isNullish(ref: ExternalInterfaceType?): Boolean = |
| js("ref == null") |
| |
| internal fun externRefToAny(ref: ExternalInterfaceType): Any? { |
| // TODO rewrite it so to get something like: |
| // block { |
| // refAsAnyref |
| // br_on_cast_fail null 0 $kotlin.Any |
| // return |
| // } |
| // If ref is an instance of kotlin class -- return it casted to Any |
| returnArgumentIfItIsKotlinAny() |
| |
| // If we have Null in notNullRef -- return null |
| // If we already have a box -- return it, |
| // otherwise -- remember new box and return it. |
| return getCachedJsObject(ref, JsExternalBox(ref).toJsReference()) |
| } |
| |
| |
| internal fun anyToExternRef(x: Any): ExternalInterfaceType { |
| return if (x is JsExternalBox) |
| x.ref |
| else |
| x.asWasmExternRef() |
| } |
| |
| internal fun stringLength(x: ExternalInterfaceType): Int = |
| js("x.length") |
| |
| // kotlin string to js string export |
| // TODO Uint16Array may work with byte endian different with Wasm (i.e. little endian) |
| internal fun importStringFromWasm(address: Int, length: Int, prefix: ExternalInterfaceType?): JsString { |
| js(""" |
| const mem16 = new Uint16Array(wasmExports.memory.buffer, address, length); |
| const str = String.fromCharCode.apply(null, mem16); |
| return (prefix == null) ? str : prefix + str; |
| """) |
| } |
| |
| internal fun kotlinToJsStringAdapter(x: String?): JsString? { |
| // Using nullable String to represent default value |
| // for parameters with default values |
| if (x == null) return null |
| if (x.isEmpty()) return jsEmptyString |
| |
| val srcArray = x.chars |
| val stringLength = srcArray.len() |
| val maxStringLength = STRING_INTEROP_MEM_BUFFER_SIZE / CHAR_SIZE_BYTES |
| |
| @OptIn(UnsafeWasmMemoryApi::class) |
| withScopedMemoryAllocator { allocator -> |
| val memBuffer = allocator.allocate(stringLength.coerceAtMost(maxStringLength) * CHAR_SIZE_BYTES).address.toInt() |
| |
| var result: ExternalInterfaceType? = null |
| var srcStartIndex = 0 |
| while (srcStartIndex < stringLength - maxStringLength) { |
| unsafeWasmCharArrayToRawMemory(srcArray, srcStartIndex, maxStringLength, memBuffer) |
| result = importStringFromWasm(memBuffer, maxStringLength, result) |
| srcStartIndex += maxStringLength |
| } |
| |
| unsafeWasmCharArrayToRawMemory(srcArray, srcStartIndex, stringLength - srcStartIndex, memBuffer) |
| return importStringFromWasm(memBuffer, stringLength - srcStartIndex, result) |
| } |
| } |
| |
| internal fun jsCheckIsNullOrUndefinedAdapter(x: ExternalInterfaceType?): ExternalInterfaceType? = |
| // We deliberately avoid usage of `takeIf` here as type erase on the inlining stage leads to infinite recursion |
| if (isNullish(x)) null else x |
| |
| // js string to kotlin string import |
| // TODO Uint16Array may work with byte endian different with Wasm (i.e. little endian) |
| internal fun jsExportStringToWasm(src: ExternalInterfaceType, srcOffset: Int, srcLength: Int, dstAddr: Int) { |
| js(""" |
| const mem16 = new Uint16Array(wasmExports.memory.buffer, dstAddr, srcLength); |
| let arrayIndex = 0; |
| let srcIndex = srcOffset; |
| while (arrayIndex < srcLength) { |
| mem16.set([src.charCodeAt(srcIndex)], arrayIndex); |
| srcIndex++; |
| arrayIndex++; |
| } |
| """) |
| } |
| |
| private const val STRING_INTEROP_MEM_BUFFER_SIZE = 65_536 // 1 page 4KiB |
| |
| internal fun jsToKotlinStringAdapter(x: ExternalInterfaceType): String { |
| val stringLength = stringLength(x) |
| val dstArray = WasmCharArray(stringLength) |
| if (stringLength == 0) { |
| return dstArray.createString() |
| } |
| |
| @OptIn(UnsafeWasmMemoryApi::class) |
| withScopedMemoryAllocator { allocator -> |
| val maxStringLength = STRING_INTEROP_MEM_BUFFER_SIZE / CHAR_SIZE_BYTES |
| val memBuffer = allocator.allocate(stringLength.coerceAtMost(maxStringLength) * CHAR_SIZE_BYTES).address.toInt() |
| |
| var srcStartIndex = 0 |
| while (srcStartIndex < stringLength - maxStringLength) { |
| jsExportStringToWasm(x, srcStartIndex, maxStringLength, memBuffer) |
| unsafeRawMemoryToWasmCharArray(memBuffer, srcStartIndex, maxStringLength, dstArray) |
| srcStartIndex += maxStringLength |
| } |
| |
| jsExportStringToWasm(x, srcStartIndex, stringLength - srcStartIndex, memBuffer) |
| unsafeRawMemoryToWasmCharArray(memBuffer, srcStartIndex, stringLength - srcStartIndex, dstArray) |
| } |
| |
| return dstArray.createString() |
| } |
| |
| |
| private fun getJsEmptyString(): JsString = |
| js("''") |
| |
| private fun getJsTrue(): JsBoolean = |
| js("true") |
| |
| private fun getJsFalse(): JsBoolean = |
| js("false") |
| |
| private var _jsEmptyString: JsString? = null |
| private val jsEmptyString: JsString |
| get() { |
| var value = _jsEmptyString |
| if (value == null) { |
| value = getJsEmptyString() |
| _jsEmptyString = value |
| } |
| |
| return value |
| } |
| |
| private var _jsTrue: JsBoolean? = null |
| private val jsTrue: JsBoolean |
| get() { |
| var value = _jsTrue |
| if (value == null) { |
| value = getJsTrue() |
| _jsTrue = value |
| } |
| |
| return value |
| } |
| |
| private var _jsFalse: JsBoolean? = null |
| private val jsFalse: JsBoolean |
| get() { |
| var value = _jsFalse |
| if (value == null) { |
| value = getJsFalse() |
| _jsFalse = value |
| } |
| |
| return value |
| } |
| |
| internal fun numberToDoubleAdapter(x: Number): Double = |
| x.toDouble() |
| |
| internal fun kotlinToJsAnyAdapter(x: Any?): ExternalInterfaceType? = |
| if (x == null) null else anyToExternRef(x) |
| |
| internal fun jsToKotlinAnyAdapter(x: ExternalInterfaceType?): Any? = |
| if (x == null) null else externRefToAny(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() |
| |
| 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) |
| |
| internal fun externRefToKotlinBooleanAdapter(x: ExternalInterfaceType): Boolean = |
| externrefToBoolean(x) |
| |
| internal fun externRefToKotlinLongAdapter(x: ExternalInterfaceType): Long = |
| externrefToLong(x) |
| |
| internal fun externRefToKotlinFloatAdapter(x: ExternalInterfaceType): Float = |
| externrefToFloat(x) |
| |
| internal fun externRefToKotlinDoubleAdapter(x: ExternalInterfaceType): Double = |
| externrefToDouble(x) |
| |
| internal fun kotlinIntToExternRefAdapter(x: Int): JsNumber = |
| intToExternref(x) |
| |
| 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): JsBigInt = |
| 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): JsBigInt = |
| kotlinULongToJsBigIntUnsafe(x.toLong()) |
| |
| internal fun kotlinLongToExternRefAdapter(x: Long): JsBigInt = |
| longToExternref(x) |
| |
| internal fun kotlinFloatToExternRefAdapter(x: Float): JsNumber = |
| floatToExternref(x) |
| |
| internal fun kotlinDoubleToExternRefAdapter(x: Double): JsNumber = |
| doubleToExternref(x) |
| |
| internal fun kotlinByteToExternRefAdapter(x: Byte): JsNumber = |
| intToExternref(x.toInt()) |
| |
| internal fun kotlinShortToExternRefAdapter(x: Short): JsNumber = |
| intToExternref(x.toInt()) |
| |
| internal fun kotlinCharToExternRefAdapter(x: Char): JsNumber = |
| intToExternref(x.code) |
| |
| internal fun newJsArray(): ExternalInterfaceType = |
| js("[]") |
| |
| internal fun jsArrayPush(array: ExternalInterfaceType, element: ExternalInterfaceType) { |
| js("array.push(element);") |
| } |