[Wasm] Imporove external interface support
* Support boxing/unboxing when casting to Any * Support ===, equals, hashCode, toString * Support adapting String in interop boundary
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.wasm.internal
|
||||
|
||||
import kotlin.wasm.internal.reftypes.anyref
|
||||
|
||||
internal external interface ExternalInterfaceType
|
||||
|
||||
internal class JsExternalBox(val ref: ExternalInterfaceType) {
|
||||
override fun toString(): String =
|
||||
externrefToString(ref)
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
if (other is JsExternalBox) {
|
||||
externrefEquals(ref, other.ref)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int =
|
||||
externrefHashCode(ref)
|
||||
}
|
||||
|
||||
//language=js
|
||||
@JsFun("""
|
||||
(() => {
|
||||
const dataView = new DataView(new ArrayBuffer(8));
|
||||
function numberHashCode(obj) {
|
||||
if ((obj | 0) === obj) {
|
||||
return obj | 0;
|
||||
} else {
|
||||
dataView.setFloat64(0, obj, true);
|
||||
return (dataView.getInt32(0, true) * 31 | 0) + dataView.getInt32(4, true) | 0;
|
||||
}
|
||||
}
|
||||
|
||||
const hashCodes = new WeakMap();
|
||||
function getObjectHashCode(obj) {
|
||||
const res = hashCodes.get(obj);
|
||||
if (res === undefined) {
|
||||
const POW_2_32 = 4294967296;
|
||||
const hash = (Math.random() * POW_2_32) | 0;
|
||||
hashCodes.set(obj, hash);
|
||||
return hash;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function getStringHashCode(str) {
|
||||
var hash = 0;
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
var code = str.charCodeAt(i);
|
||||
hash = (hash * 31 + code) | 0;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
return (obj) => {
|
||||
if (obj == null) {
|
||||
return 0;
|
||||
}
|
||||
switch (typeof obj) {
|
||||
case "object":
|
||||
case "function":
|
||||
return getObjectHashCode(obj);
|
||||
case "number":
|
||||
return numberHashCode(obj);
|
||||
case "boolean":
|
||||
return obj;
|
||||
default:
|
||||
return getStringHashCode(String(obj));
|
||||
}
|
||||
}
|
||||
})()"""
|
||||
)
|
||||
private external fun externrefHashCode(ref: ExternalInterfaceType): Int
|
||||
|
||||
@JsFun("ref => String(ref)")
|
||||
private external fun externrefToString(ref: ExternalInterfaceType): String
|
||||
|
||||
@JsFun("(lhs, rhs) => lhs === rhs")
|
||||
private external fun externrefEquals(lhs: ExternalInterfaceType, rhs: ExternalInterfaceType): Boolean
|
||||
|
||||
@JsFun("(ref) => (typeof ref !== 'object' || ref === null) ? null : (externrefBoxes.get(ref) ?? null)")
|
||||
private external fun getExternrefBoxOrNull(ref: ExternalInterfaceType): JsExternalBox?
|
||||
|
||||
@JsFun("(ref, box) => { if (typeof ref === 'object' && ref !== null) { externrefBoxes.set(ref, box); } }")
|
||||
private external fun setExternrefBox(ref: ExternalInterfaceType, box: JsExternalBox)
|
||||
|
||||
@WasmNoOpCast
|
||||
@Suppress("unused")
|
||||
internal fun Any?.asWasmAnyref(): anyref =
|
||||
implementedAsIntrinsic
|
||||
|
||||
@WasmNoOpCast
|
||||
@Suppress("unused")
|
||||
internal fun ExternalInterfaceType.externAsWasmAnyref(): anyref =
|
||||
implementedAsIntrinsic
|
||||
|
||||
@WasmNoOpCast
|
||||
@Suppress("unused")
|
||||
internal fun Any?.asWasmExternRef(): ExternalInterfaceType =
|
||||
implementedAsIntrinsic
|
||||
|
||||
|
||||
@JsFun("(ref) => ref == null")
|
||||
internal external fun isNullish(ref: ExternalInterfaceType): Boolean
|
||||
|
||||
internal fun externRefToAny(ref: ExternalInterfaceType): Any? {
|
||||
// If ref is an instance of kotlin class -- return it cased to Any
|
||||
val refAsAnyref = ref.externAsWasmAnyref()
|
||||
if (wasm_ref_is_data(refAsAnyref)) {
|
||||
val refAsDataRef = wasm_ref_as_data(refAsAnyref)
|
||||
if (wasm_ref_test<Any>(refAsDataRef)) {
|
||||
return wasm_ref_cast<Any>(refAsDataRef)
|
||||
}
|
||||
}
|
||||
|
||||
if (isNullish(ref))
|
||||
return null
|
||||
|
||||
// If we already have a box -- return it,
|
||||
// otherwise -- create a new box and remember it.
|
||||
return getExternrefBoxOrNull(ref)
|
||||
?: JsExternalBox(ref).also {
|
||||
setExternrefBox(ref, it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun anyToExternRef(x: Any): ExternalInterfaceType {
|
||||
return if (x is JsExternalBox)
|
||||
x.ref
|
||||
else
|
||||
x.asWasmExternRef()
|
||||
}
|
||||
|
||||
internal external fun importStringFromWasm(addr: Int): ExternalInterfaceType
|
||||
|
||||
@JsFun("x => x.length")
|
||||
internal external fun stringLength(x: ExternalInterfaceType): Int
|
||||
|
||||
internal fun convertJsStringToKotlinString(x: ExternalInterfaceType): String {
|
||||
val length = stringLength(x)
|
||||
val addr = unsafeGetScratchRawMemory(INT_SIZE_BYTES + length * CHAR_SIZE_BYTES)
|
||||
jsWriteStringIntoMemory(x, addr)
|
||||
return kotlin.String(unsafeRawMemoryToCharArray(addr, length))
|
||||
}
|
||||
|
||||
|
||||
//language=js
|
||||
@JsFun(
|
||||
""" (str, addr) => {
|
||||
const memory = new DataView(wasmInstance.exports.memory.buffer);
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
memory.setInt16(addr + i * 2, str.charCodeAt(i), true);
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
internal external fun jsWriteStringIntoMemory(str: ExternalInterfaceType, addr: Int)
|
||||
|
||||
|
||||
internal fun kotlinToJsStringAdapter(x: String): ExternalInterfaceType {
|
||||
return importStringFromWasm(exportString(x))
|
||||
}
|
||||
|
||||
@JsFun("() => true")
|
||||
internal external fun jsTrue(): ExternalInterfaceType
|
||||
|
||||
@JsFun("() => false")
|
||||
internal external fun jsFalse(): ExternalInterfaceType
|
||||
|
||||
internal fun kotlinToJsBooleanAdapter(x: Boolean): ExternalInterfaceType =
|
||||
if (x) jsTrue() else jsFalse()
|
||||
|
||||
internal fun kotlinToJsAnyAdapter(x: Any): ExternalInterfaceType =
|
||||
anyToExternRef(x)
|
||||
|
||||
internal fun jsToKotlinAnyAdapter(x: ExternalInterfaceType): Any? =
|
||||
externRefToAny(x)
|
||||
|
||||
internal fun jsToKotlinStringAdapter(x: ExternalInterfaceType): String =
|
||||
convertJsStringToKotlinString(x)
|
||||
|
||||
internal fun jsToKotlinByteAdapter(x: Int): Byte = x.toByte()
|
||||
internal fun jsToKotlinShortAdapter(x: Int): Short = x.toShort()
|
||||
internal fun jsToKotlinCharAdapter(x: Int): Char = x.toChar()
|
||||
@@ -9,6 +9,11 @@
|
||||
|
||||
package kotlin.wasm.internal
|
||||
|
||||
import kotlin.wasm.internal.reftypes.anyref
|
||||
import kotlin.wasm.internal.reftypes.dataref
|
||||
import kotlin.wasm.internal.reftypes.funcref
|
||||
import kotlin.wasm.internal.reftypes.i31ref
|
||||
|
||||
@WasmOp(WasmOp.UNREACHABLE)
|
||||
internal fun wasm_unreachable(): Nothing =
|
||||
implementedAsIntrinsic
|
||||
@@ -329,4 +334,25 @@ public external fun wasm_i64_clz(a: Long): Long
|
||||
public external fun wasm_i64_popcnt(a: Long): Long
|
||||
|
||||
@WasmOp(WasmOp.I64_CTZ)
|
||||
public external fun wasm_i64_ctz(a: Long): Long
|
||||
public external fun wasm_i64_ctz(a: Long): Long
|
||||
|
||||
|
||||
// Reference type operators
|
||||
|
||||
@WasmOp(WasmOp.REF_IS_FUNC)
|
||||
internal external fun wasm_ref_is_func(x: anyref): Boolean
|
||||
|
||||
@WasmOp(WasmOp.REF_IS_DATA)
|
||||
internal external fun wasm_ref_is_data(x: anyref): Boolean
|
||||
|
||||
@WasmOp(WasmOp.REF_IS_I31)
|
||||
internal external fun wasm_ref_is_i31(x: anyref): Boolean
|
||||
|
||||
@WasmOp(WasmOp.REF_AS_FUNC)
|
||||
internal external fun wasm_ref_as_func(x: anyref): funcref
|
||||
|
||||
@WasmOp(WasmOp.REF_AS_DATA)
|
||||
internal external fun wasm_ref_as_data(x: anyref): dataref
|
||||
|
||||
@WasmOp(WasmOp.REF_AS_I31)
|
||||
internal external fun wasm_ref_as_i31(x: anyref): i31ref
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:ExcludedFromCodegen
|
||||
|
||||
package kotlin.wasm.internal.reftypes
|
||||
|
||||
import kotlin.wasm.internal.*
|
||||
|
||||
// These interfaces correspond to Wasm GC reference types with the same name.
|
||||
// They are not proper Kotlin interfaces and should be used with care.
|
||||
//
|
||||
// WARNING! Do not upcast to Any, nullable types, type parameters, etc.
|
||||
// Do not use Any methods
|
||||
// Do not use type operators `is`, `as`, etc.
|
||||
// Do not use ==, ===
|
||||
//
|
||||
// Use dedicated intrinsics instead.
|
||||
|
||||
internal interface anyref
|
||||
internal interface eqref : anyref
|
||||
internal interface dataref : eqref
|
||||
internal interface i31ref : eqref
|
||||
internal interface funcref : anyref
|
||||
@@ -216,6 +216,12 @@ internal annotation class WasmOp(val name: String) {
|
||||
const val RETURN = "RETURN"
|
||||
const val CALL = "CALL"
|
||||
const val CALL_INDIRECT = "CALL_INDIRECT"
|
||||
const val TRY = "TRY"
|
||||
const val CATCH = "CATCH"
|
||||
const val CATCH_ALL = "CATCH_ALL"
|
||||
const val DELEGATE = "DELEGATE"
|
||||
const val THROW = "THROW"
|
||||
const val RETHROW = "RETHROW"
|
||||
const val DROP = "DROP"
|
||||
const val SELECT = "SELECT"
|
||||
const val SELECT_TYPED = "SELECT_TYPED"
|
||||
@@ -255,5 +261,19 @@ internal annotation class WasmOp(val name: String) {
|
||||
const val REF_TEST = "REF_TEST"
|
||||
const val REF_CAST = "REF_CAST"
|
||||
const val BR_ON_CAST = "BR_ON_CAST"
|
||||
const val BR_ON_CAST_FAIL = "BR_ON_CAST_FAIL"
|
||||
const val REF_IS_FUNC = "REF_IS_FUNC"
|
||||
const val REF_IS_DATA = "REF_IS_DATA"
|
||||
const val REF_IS_I31 = "REF_IS_I31"
|
||||
const val REF_AS_FUNC = "REF_AS_FUNC"
|
||||
const val REF_AS_DATA = "REF_AS_DATA"
|
||||
const val REF_AS_I31 = "REF_AS_I31"
|
||||
const val BR_ON_FUNC = "BR_ON_FUNC"
|
||||
const val BR_ON_DATA = "BR_ON_DATA"
|
||||
const val BR_ON_I31 = "BR_ON_I31"
|
||||
const val BR_ON_NON_FUNC = "BR_ON_NON_FUNC"
|
||||
const val BR_ON_NON_DATA = "BR_ON_NON_DATA"
|
||||
const val BR_ON_NON_I31 = "BR_ON_NON_I31"
|
||||
const val GET_UNIT = "GET_UNIT"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,4 +13,18 @@ package kotlin.js
|
||||
@ExperimentalJsExport
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
public actual annotation class JsExport
|
||||
public actual annotation class JsExport
|
||||
|
||||
/**
|
||||
* Specifies JavaScript name for external and imported declarations
|
||||
*/
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(
|
||||
AnnotationTarget.CLASS,
|
||||
AnnotationTarget.FUNCTION,
|
||||
AnnotationTarget.PROPERTY,
|
||||
AnnotationTarget.CONSTRUCTOR,
|
||||
AnnotationTarget.PROPERTY_GETTER,
|
||||
AnnotationTarget.PROPERTY_SETTER
|
||||
)
|
||||
public actual annotation class JsName(actual val name: String)
|
||||
Reference in New Issue
Block a user