[Wasm] Backend support for wasi mode

This commit is contained in:
Igor Yakovlev
2023-07-31 15:43:48 +02:00
committed by Space Team
parent 22a3000789
commit 5e14ccbccc
8 changed files with 107 additions and 51 deletions
@@ -23,6 +23,8 @@ import org.jetbrains.kotlin.ir.types.*
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.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.WasmTarget
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.scopes.MemberScope
@@ -78,8 +80,6 @@ class WasmSymbols(
internal val initAssociatedObjects = getInternalFunction("initAssociatedObjects")
internal val addAssociatedObject = getInternalFunction("addAssociatedObject")
internal val throwAsJsException: IrSimpleFunctionSymbol = getInternalFunction("throwAsJsException")
override val throwNullPointerException = getInternalFunction("THROW_NPE")
override val throwISE = getInternalFunction("THROW_ISE")
override val throwTypeCastException = getInternalFunction("THROW_CCE")
@@ -189,7 +189,6 @@ class WasmSymbols(
val booleanAnd = getInternalFunction("wasm_i32_and")
val refEq = getInternalFunction("wasm_ref_eq")
val refIsNull = getInternalFunction("wasm_ref_is_null")
val externRefIsNull = getInternalFunction("wasm_externref_is_null")
val refTest = getInternalFunction("wasm_ref_test")
val refCastNull = getInternalFunction("wasm_ref_cast_null")
val wasmArrayCopy = getInternalFunction("wasm_array_copy")
@@ -237,9 +236,6 @@ class WasmSymbols(
val unsafeGetScratchRawMemory = getInternalFunction("unsafeGetScratchRawMemory")
val returnArgumentIfItIsKotlinAny = getInternalFunction("returnArgumentIfItIsKotlinAny")
val newJsArray = getInternalFunction("newJsArray")
val jsArrayPush = getInternalFunction("jsArrayPush")
val startCoroutineUninterceptedOrReturnIntrinsics =
(0..2).map { getInternalFunction("startCoroutineUninterceptedOrReturnIntrinsic$it") }
@@ -309,9 +305,6 @@ class WasmSymbols(
val wasmAnyRefClass = getIrClass(FqName("kotlin.wasm.internal.reftypes.anyref"))
private val jsAnyClass = getIrClass(FqName("kotlin.js.JsAny"))
val jsAnyType by lazy { jsAnyClass.defaultType }
inner class JsInteropAdapters {
val kotlinToJsStringAdapter = getInternalFunction("kotlinToJsStringAdapter")
val kotlinToJsAnyAdapter = getInternalFunction("kotlinToJsAnyAdapter")
@@ -342,18 +335,42 @@ class WasmSymbols(
val kotlinCharToExternRefAdapter = getInternalFunction("kotlinCharToExternRefAdapter")
}
val jsInteropAdapters = JsInteropAdapters()
inner class JsRelatedSymbols {
val jsInteropAdapters = JsInteropAdapters()
private val jsExportClass = getIrClass(FqName("kotlin.js.JsExport"))
val jsExportConstructor by lazy { jsExportClass.constructors.single() }
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 val jsNameClass = getIrClass(FqName("kotlin.js.JsName"))
val jsNameConstructor by lazy { jsNameClass.constructors.single() }
private val jsFunClass = getIrClass(FqName("kotlin.JsFun"))
val jsFunConstructor by lazy { jsFunClass.constructors.single() }
private val jsFunClass = getIrClass(FqName("kotlin.JsFun"))
val jsFunConstructor by lazy { jsFunClass.constructors.single() }
val jsCode = getFunction("js", kotlinJsPackage)
val jsCode = getFunction("js", kotlinJsPackage)
val jsAnyType: IrType by lazy { getIrClass(FqName("kotlin.js.JsAny")).defaultType }
val newJsArray = getInternalFunction("newJsArray")
val jsArrayPush = getInternalFunction("jsArrayPush")
val externRefIsNull = getInternalFunction("wasm_externref_is_null")
internal val throwAsJsException: IrSimpleFunctionSymbol =
getInternalFunction("throwAsJsException")
}
private val wasmExportClass = getIrClass(FqName("kotlin.wasm.WasmExport"))
val wasmExportConstructor by lazy { wasmExportClass.constructors.single() }
private val jsRelatedSymbolsIfNonWasi =
when (context.configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS) == WasmTarget.JS) {
true -> JsRelatedSymbols()
else -> null
}
val jsRelatedSymbols get() = jsRelatedSymbolsIfNonWasi ?: error("Cannot access to js related std in wasi mode")
private fun findClass(memberScope: MemberScope, name: Name): ClassDescriptor =
memberScope.getContributedClassifier(name, NoLookupLocation.FROM_BACKEND) as ClassDescriptor
@@ -381,7 +398,7 @@ class WasmSymbols(
return symbolTable.descriptorExtension.referenceSimpleFunction(tmp.single())
}
private fun getInternalFunction(name: String) = getFunction(name, wasmInternalPackage)
private fun getInternalFunction(name: String): IrSimpleFunctionSymbol = getFunction(name, wasmInternalPackage)
private fun getEnumsFunction(name: String) = getFunction(name, enumsInternalPackage)
@@ -25,6 +25,8 @@ import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.WasmTarget
import org.jetbrains.kotlin.name.parentOrNull
class BuiltInsLowering(val context: WasmBackendContext) : FileLoweringPass {
@@ -56,7 +58,13 @@ class BuiltInsLowering(val context: WasmBackendContext) : FileLoweringPass {
irBuiltins.eqeqSymbol,
irBuiltins.eqeqeqSymbol -> {
fun callRefIsNull(expr: IrExpression): IrCall {
val refIsNull = if (expr.type.erasedUpperBound?.isExternal == true) symbols.externRefIsNull else symbols.refIsNull
if (
context.configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS) == WasmTarget.WASI &&
expr.type.erasedUpperBound?.isExternal == true
) {
error("Unexpected external refs in wasi mode")
}
val refIsNull = if (expr.type.erasedUpperBound?.isExternal == true) symbols.jsRelatedSymbols.externRefIsNull else symbols.refIsNull
return builder.irCall(refIsNull).apply { putValueArgument(0, expr) }
}
@@ -418,7 +418,7 @@ fun createExternalJsFunction(
isExternal = true
}
val builder = context.createIrBuilder(res.symbol)
res.annotations += builder.irCallConstructor(context.wasmSymbols.jsFunConstructor, typeArguments = emptyList()).also {
res.annotations += builder.irCallConstructor(context.wasmSymbols.jsRelatedSymbols.jsFunConstructor, typeArguments = emptyList()).also {
it.putValueArgument(0, builder.irString(jsCode))
}
return res
@@ -13,12 +13,17 @@ import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.WasmTarget
/**
* Lower calls to `js(code)` into `@JsFun(code) external` functions.
*/
class JsCodeCallsLowering(val context: WasmBackendContext) : FileLoweringPass {
private val jsRelatedSymbols get() = context.wasmSymbols.jsRelatedSymbols
override fun lower(irFile: IrFile) {
if (context.configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS) == WasmTarget.WASI) return
irFile.transformDeclarationsFlat { declaration ->
when (declaration) {
is IrSimpleFunction -> transformFunction(declaration)
@@ -81,7 +86,7 @@ class JsCodeCallsLowering(val context: WasmBackendContext) : FileLoweringPass {
}
val builder = context.createIrBuilder(function.symbol)
function.annotations += builder.irCallConstructor(context.wasmSymbols.jsFunConstructor, typeArguments = emptyList()).also {
function.annotations += builder.irCallConstructor(jsRelatedSymbols.jsFunConstructor, typeArguments = emptyList()).also {
it.putValueArgument(0, builder.irString(jsFunCode))
}
function.body = null
@@ -106,7 +111,7 @@ class JsCodeCallsLowering(val context: WasmBackendContext) : FileLoweringPass {
private fun IrExpression.getJsCode(): String? {
val call = this as? IrCall ?: return null
if (call.symbol != context.wasmSymbols.jsCode) return null
if (call.symbol != jsRelatedSymbols.jsCode) return null
@Suppress("UNCHECKED_CAST")
return (call.getValueArgument(0) as IrConst<String>).value
}
@@ -31,6 +31,8 @@ import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.WasmTarget
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -41,12 +43,15 @@ import org.jetbrains.kotlin.util.OperatorNameConventions
class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationTransformer {
val builtIns = context.irBuiltIns
val symbols = context.wasmSymbols
val adapters = symbols.jsInteropAdapters
val jsRelatedSymbols get() = context.wasmSymbols.jsRelatedSymbols
val adapters get() = jsRelatedSymbols.jsInteropAdapters
val additionalDeclarations = mutableListOf<IrDeclaration>()
lateinit var currentParent: IrDeclarationParent
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (context.configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS) == WasmTarget.WASI) return null
if (declaration.isFakeOverride) return null
if (declaration !is IrSimpleFunction) return null
val isExported = declaration.isJsExport()
@@ -176,10 +181,10 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
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 {
newFun.annotations += builder.irCallConstructor(jsRelatedSymbols.jsNameConstructor, typeArguments = emptyList()).also {
it.putValueArgument(0, builder.irString(function.getJsNameOrKotlinName().identifier))
}
function.annotations = function.annotations.filter { it.symbol != context.wasmSymbols.jsExportConstructor }
function.annotations = function.annotations.filter { it.symbol != jsRelatedSymbols.jsExportConstructor }
return listOf(function, newFun)
}
@@ -202,15 +207,17 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
)
}
val primitivesToExternRefAdapters: Map<IrType, InteropTypeAdapter> = mapOf(
builtIns.byteType to adapters.kotlinByteToExternRefAdapter,
builtIns.shortType to adapters.kotlinShortToExternRefAdapter,
builtIns.charType to adapters.kotlinCharToExternRefAdapter,
builtIns.intType to adapters.kotlinIntToExternRefAdapter,
builtIns.longType to adapters.kotlinLongToExternRefAdapter,
builtIns.floatType to adapters.kotlinFloatToExternRefAdapter,
builtIns.doubleType to adapters.kotlinDoubleToExternRefAdapter,
).mapValues { FunctionBasedAdapter(it.value.owner) }
val primitivesToExternRefAdapters: Map<IrType, InteropTypeAdapter> by lazy {
mapOf(
builtIns.byteType to adapters.kotlinByteToExternRefAdapter,
builtIns.shortType to adapters.kotlinShortToExternRefAdapter,
builtIns.charType to adapters.kotlinCharToExternRefAdapter,
builtIns.intType to adapters.kotlinIntToExternRefAdapter,
builtIns.longType to adapters.kotlinLongToExternRefAdapter,
builtIns.floatType to adapters.kotlinFloatToExternRefAdapter,
builtIns.doubleType to adapters.kotlinDoubleToExternRefAdapter,
).mapValues { FunctionBasedAdapter(it.value.owner) }
}
private fun IrType.kotlinToJsAdapterIfNeeded(isReturn: Boolean): InteropTypeAdapter? {
if (isReturn && this == builtIns.unitType)
@@ -458,7 +465,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
}
// TODO find out a better way to export the such declarations only when it's required. Also, fix building roots for DCE, then.
result.annotations += builder.irCallConstructor(context.wasmSymbols.jsExportConstructor, typeArguments = emptyList())
result.annotations += builder.irCallConstructor(jsRelatedSymbols.jsExportConstructor, typeArguments = emptyList())
additionalDeclarations += result
return result
}
@@ -466,7 +473,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
private fun createKotlinToJsClosureConvertor(info: FunctionTypeInfo): IrSimpleFunction {
val result = context.irFactory.buildFun {
name = Name.identifier("__convertKotlinClosureToJsClosure_${info.signatureString}")
returnType = context.wasmSymbols.jsAnyType
returnType = jsRelatedSymbols.jsAnyType
isExternal = true
}
result.parent = currentParent
@@ -487,7 +494,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
append(")")
}
result.annotations += builder.irCallConstructor(context.wasmSymbols.jsFunConstructor, typeArguments = emptyList()).also {
result.annotations += builder.irCallConstructor(jsRelatedSymbols.jsFunConstructor, typeArguments = emptyList()).also {
it.putValueArgument(0, builder.irString(jsCode))
}
@@ -507,7 +514,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
result.parent = currentParent
result.addValueParameter {
name = Name.identifier("f")
type = context.wasmSymbols.jsAnyType
type = jsRelatedSymbols.jsAnyType
}
val closureClass = context.irFactory.buildClass {
@@ -520,7 +527,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
val closureClassField = closureClass.addField {
name = Name.identifier("jsClosure")
type = context.wasmSymbols.jsAnyType
type = jsRelatedSymbols.jsAnyType
visibility = DescriptorVisibilities.PRIVATE
isFinal = true
}
@@ -589,7 +596,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
result.parent = currentParent
result.addValueParameter {
name = Name.identifier("f")
type = symbols.jsAnyType
type = jsRelatedSymbols.jsAnyType
}
val arity = info.adaptedParameterTypes.size
repeat(arity) { paramIndex ->
@@ -607,7 +614,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
append(")")
}
result.annotations += builder.irCallConstructor(context.wasmSymbols.jsFunConstructor, typeArguments = emptyList()).also {
result.annotations += builder.irCallConstructor(jsRelatedSymbols.jsFunConstructor, typeArguments = emptyList()).also {
it.putValueArgument(0, builder.irString(jsFun))
}
@@ -817,7 +824,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
private val fromElementType: IrType,
) : InteropTypeAdapter {
override val toType: IrType =
context.wasmSymbols.jsAnyType
jsRelatedSymbols.jsAnyType
private val elementAdapter =
primitivesToExternRefAdapters[fromElementType]
@@ -837,7 +844,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
// newJsArray.push(adapt(originalArray[index]));
// index++
// }
val newJsArrayVar = irTemporary(irCall(symbols.newJsArray))
val newJsArrayVar = irTemporary(irCall(jsRelatedSymbols.newJsArray))
val indexVar = irTemporary(irInt(0), isMutable = true)
val arraySizeVar = irTemporary(irCall(sizeMethod).apply { dispatchReceiver = irGet(originalArrayVar) })
@@ -854,7 +861,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
),
this@irBlock
)
+irCall(symbols.jsArrayPush).apply {
+irCall(jsRelatedSymbols.jsArrayPush).apply {
putValueArgument(0, irGet(newJsArrayVar))
putValueArgument(1, adaptedValue)
}
@@ -885,6 +892,7 @@ internal fun StringBuilder.appendParameterList(size: Int, name: String = "p", is
*/
class JsInteropFunctionCallsLowering(val context: WasmBackendContext) : BodyLoweringPass {
override fun lower(irBody: IrBody, container: IrDeclaration) {
if (context.configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS) == WasmTarget.WASI) return
irBody.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitCall(expression: IrCall): IrExpression {
expression.transformChildrenVoid()
@@ -20,6 +20,8 @@ import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
import org.jetbrains.kotlin.ir.expressions.IrExpressionBody
import org.jetbrains.kotlin.ir.util.toIrConst
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.WasmTarget
import org.jetbrains.kotlin.name.Name
// This pass needed to wrap around unhandled exceptions from JsExport functions and throw JS exception for call from JS site
@@ -47,7 +49,7 @@ import org.jetbrains.kotlin.name.Name
internal class UnhandledExceptionLowering(val context: WasmBackendContext) : FileLoweringPass {
private val throwableType = context.irBuiltIns.throwableType
private val irBooleanType = context.wasmSymbols.irBuiltIns.booleanType
private val throwAsJsException = context.wasmSymbols.throwAsJsException
private val throwAsJsException get() = context.wasmSymbols.jsRelatedSymbols.throwAsJsException
private val isNotFirstWasmExportCallGetter = context.wasmSymbols.isNotFirstWasmExportCall.owner.getter!!.symbol
private val isNotFirstWasmExportCallSetter = context.wasmSymbols.isNotFirstWasmExportCall.owner.setter!!.symbol
@@ -131,6 +133,7 @@ internal class UnhandledExceptionLowering(val context: WasmBackendContext) : Fil
}
override fun lower(irFile: IrFile) {
if (context.configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS) == WasmTarget.WASI) return
for (declaration in irFile.declarations) {
if (declaration is IrFunction && declaration.isExported()) {
processExportFunction(declaration)
@@ -23,6 +23,8 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.WasmTarget
class WasmTypeOperatorLowering(val context: WasmBackendContext) : FileLoweringPass {
@@ -34,11 +36,11 @@ class WasmTypeOperatorLowering(val context: WasmBackendContext) : FileLoweringPa
class WasmBaseTypeOperatorTransformer(val context: WasmBackendContext) : IrElementTransformerVoidWithContext() {
private val symbols = context.wasmSymbols
private val builtIns = context.irBuiltIns
private val jsToKotlinAnyAdapter get() = symbols.jsRelatedSymbols.jsInteropAdapters.jsToKotlinAnyAdapter
private val kotlinToJsAnyAdapter get() = symbols.jsRelatedSymbols.jsInteropAdapters.kotlinToJsAnyAdapter
private lateinit var builder: DeclarationIrBuilder
override fun visitSimpleFunction(declaration: IrSimpleFunction): IrStatement {
return super.visitSimpleFunction(declaration)
}
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
super.visitTypeOperator(expression)
builder = context.createIrBuilder(currentScope!!.scope.scopeOwnerSymbol).at(expression)
@@ -213,7 +215,10 @@ class WasmBaseTypeOperatorTransformer(val context: WasmBackendContext) : IrEleme
}
if (fromClass.isExternal && !toClass.isExternal) {
val narrowingToAny = builder.irCall(symbols.jsInteropAdapters.jsToKotlinAnyAdapter).also {
if (context.configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS) == WasmTarget.WASI) {
TODO("Implement externalize adapter for wasi mode")
}
val narrowingToAny = builder.irCall(jsToKotlinAnyAdapter).also {
it.putValueArgument(0, value)
}
// Continue narrowing from Any to expected type
@@ -221,7 +226,10 @@ class WasmBaseTypeOperatorTransformer(val context: WasmBackendContext) : IrEleme
}
if (toClass.isExternal && !fromClass.isExternal) {
return builder.irCall(symbols.jsInteropAdapters.kotlinToJsAnyAdapter).also {
if (context.configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS) == WasmTarget.WASI) {
TODO("Implement internalize adapter for wasi mode")
}
return builder.irCall(kotlinToJsAnyAdapter).also {
it.putValueArgument(0, value)
}
}
@@ -11,17 +11,24 @@ 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.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.WasmTarget
import org.jetbrains.kotlin.name.FqName
/**
* Mark declarations from [exportedFqNames] with @JsExport annotation
*/
fun markExportedDeclarations(context: WasmBackendContext, irFile: IrFile, exportedFqNames: Set<FqName>) {
val exportConstructor = when (context.configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS)) {
WasmTarget.WASI -> context.wasmSymbols.wasmExportConstructor
else -> context.wasmSymbols.jsRelatedSymbols.jsExportConstructor
}
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())
builder.irCallConstructor(exportConstructor, typeArguments = emptyList())
}
}
}