f318b5969d
Substitution of type arguments to non-reified type parameters may lead to accidental reification, which should not be done (see ^KT-60174 for examples). So, we should erase them, except the few cases. ^KT-60174: Fixed ^KT-60175: Fixed
411 lines
12 KiB
Kotlin
411 lines
12 KiB
Kotlin
/*
|
|
* 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")
|
|
|
|
private external fun tryGetOrSetExternrefBox(
|
|
ref: ExternalInterfaceType,
|
|
ifNotCached: JsReference<JsExternalBox>
|
|
): JsReference<JsExternalBox>?
|
|
|
|
@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 tryGetOrSetExternrefBox(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);")
|
|
}
|