diff --git a/gradle.properties b/gradle.properties index ad4d259..bc2582c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,5 +10,5 @@ kotlin.mpp.import.enableKgpDependencyResolution=true android.useAndroidX=true -knee.version=1.2.0 +knee.version=1.3.0 knee.group=io.deepmedia.tools.knee diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 27313fb..30006bd 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/knee-compiler-plugin/build.gradle.kts b/knee-compiler-plugin/build.gradle.kts index 859b857..15efb1d 100644 --- a/knee-compiler-plugin/build.gradle.kts +++ b/knee-compiler-plugin/build.gradle.kts @@ -2,7 +2,7 @@ plugins { kotlin("jvm") kotlin("plugin.serialization") id("io.deepmedia.tools.deployer") - id("com.github.johnrengelman.shadow") version "8.1.1" + id("com.gradleup.shadow") version "9.4.1" } dependencies { diff --git a/knee-compiler-plugin/src/main/kotlin/Classes.kt b/knee-compiler-plugin/src/main/kotlin/Classes.kt index 52048bc..67255c6 100644 --- a/knee-compiler-plugin/src/main/kotlin/Classes.kt +++ b/knee-compiler-plugin/src/main/kotlin/Classes.kt @@ -28,11 +28,13 @@ import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.types.KotlinType fun preprocessClass(klass: KneeClass, context: KneeContext) { - context.mapper.register(ClassCodec( - symbols = context.symbols, - irClass = klass.source, - irConstructors = klass.constructors.map { it.source.symbol }, - )) + context.mapper.register( + ClassCodec( + symbols = context.symbols, + irClass = klass.source, + irConstructors = klass.constructors.map { it.source.symbol }, + ) + ) } fun processClass(klass: KneeClass, context: KneeContext, codegen: KneeCodegen, initInfo: InitInfo) { @@ -57,7 +59,7 @@ fun processClass(klass: KneeClass, context: KneeContext, codegen: KneeCodegen, i * because we already create a KneeFunction for it. Just make sure it gets the right name. */ private fun KneeClass.makeCodegen(codegen: KneeCodegen) { - val container = codegen.prepareContainer(source, importInfo) + val container = codegen.ensureContainer(source, importInfo) codegenClone = container.addChildIfNeeded(CodegenClass(source.asTypeSpec())).apply { if (codegen.verbose) spec.addKdoc("knee:classes") spec.addModifiers(source.visibility.asModifier()) @@ -80,6 +82,7 @@ class ClassCodec( fun encodedTypeForFir(module: org.jetbrains.kotlin.descriptors.ModuleDescriptor): KotlinType { return module.builtIns.longType } + fun encodedTypeForIr(symbols: KneeSymbols): JniType { return JniType.Long(symbols) } @@ -93,18 +96,24 @@ class ClassCodec( * We must create a stable ref for this class so that it can be passed to the frontend. * In addition, if this class is owned by some other, we must add the stable ref to the owner list. */ - override fun IrStatementsBuilder<*>.irEncode(irContext: IrCodecContext, local: IrValueDeclaration): IrExpression { + override fun IrStatementsBuilder<*>.irEncode( + irContext: IrCodecContext, + local: IrValueDeclaration + ): IrExpression { return irCall(encode).apply { - putValueArgument(0, irGet(local)) + arguments[0] = irGet(local) } } // NOTE: in theory it is possible here to check whether this is a disposer and if it is, // release the stable refs here. - override fun IrStatementsBuilder<*>.irDecode(irContext: IrCodecContext, jni: IrValueDeclaration): IrExpression { + override fun IrStatementsBuilder<*>.irDecode( + irContext: IrCodecContext, + jni: IrValueDeclaration + ): IrExpression { return irCall(decode).apply { - putTypeArgument(0, localIrType) - putValueArgument(0, irGet(jni)) + typeArguments[0] = localIrType + arguments[0] = irGet(jni) } } @@ -113,7 +122,10 @@ class ClassCodec( * accepting the reference. If this is a constructor, we should instead call this(knee = $bridge), * which means returning bridge value with no edits. */ - override fun CodeBlock.Builder.codegenDecode(codegenContext: CodegenCodecContext, jni: String): String { + override fun CodeBlock.Builder.codegenDecode( + codegenContext: CodegenCodecContext, + jni: String + ): String { val isConstructor = codegenContext.functionSymbol in irConstructors return when { isConstructor -> jni @@ -124,7 +136,10 @@ class ClassCodec( /** * A JVM class must reach the native world. This means that we must pass the native reference instead. */ - override fun CodeBlock.Builder.codegenEncode(codegenContext: CodegenCodecContext, local: String): String { + override fun CodeBlock.Builder.codegenEncode( + codegenContext: CodegenCodecContext, + local: String + ): String { return "$local.`${InstancesCodegen.HandleField}`" } } \ No newline at end of file diff --git a/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt b/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt index da93c3a..e6d70cc 100644 --- a/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt +++ b/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt @@ -9,6 +9,7 @@ import io.deepmedia.tools.knee.plugin.compiler.features.KneeDownwardFunction.Kin import io.deepmedia.tools.knee.plugin.compiler.functions.DownwardFunctionSignature import io.deepmedia.tools.knee.plugin.compiler.functions.DownwardFunctionsCodegen import io.deepmedia.tools.knee.plugin.compiler.functions.DownwardFunctionsIr +import io.deepmedia.tools.knee.plugin.compiler.functions.ensureImportedAdapter import io.deepmedia.tools.knee.plugin.compiler.codec.CodegenCodecContext import io.deepmedia.tools.knee.plugin.compiler.codec.IrCodecContext import io.deepmedia.tools.knee.plugin.compiler.context.KneeLogger @@ -23,19 +24,30 @@ import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter import org.jetbrains.kotlin.ir.builders.declarations.buildVariable import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin +import org.jetbrains.kotlin.ir.declarations.IrParameterKind import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl import org.jetbrains.kotlin.ir.types.isAny import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.Name -fun processDownwardFunction(function: KneeDownwardFunction, context: KneeContext, codegen: KneeCodegen, initInfo: InitInfo) { +fun processDownwardFunction( + function: KneeDownwardFunction, + context: KneeContext, + codegen: KneeCodegen, + initInfo: InitInfo +) { val signature = DownwardFunctionSignature(function.source, function.kind, context) + function.ensureImportedAdapter(context, signature) function.makeIr(context, signature, initInfo) function.makeCodegen(codegen, signature, context.log) } -private fun KneeDownwardFunction.makeCodegen(codegen: KneeCodegen, signature: DownwardFunctionSignature, logger: KneeLogger) { +private fun KneeDownwardFunction.makeCodegen( + codegen: KneeCodegen, + signature: DownwardFunctionSignature, + logger: KneeLogger +) { // Unlike IR, we have to generate both the bridge function and the local function. // First we make the local function, whose implementation will call the bridge val localName = source.name.asString() @@ -48,6 +60,7 @@ private fun KneeDownwardFunction.makeCodegen(codegen: KneeCodegen, signature: Do kind is Kind.ClassConstructor -> FunSpec .constructorBuilder() .addModifiers(source.visibility.asModifier()) + else -> FunSpec .builder(localName) .addModifiers(source.visibility.asModifier()) @@ -89,11 +102,16 @@ private fun KneeDownwardFunction.makeCodegen(codegen: KneeCodegen, signature: Do // Exclude prefixes, they only refer to bridge functions signature.regularParameters.forEach { (param, codec) -> val name = param.asStringSafeForCodegen(true) - val defaultValue = source.valueParameters.firstOrNull { it.name == param }?.defaultValueForCodegen(expectSources) + val defaultValue = source.parameters + .filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + .firstOrNull { it.name == param } + ?.defaultValueForCodegen(expectSources) // addParameter(name, codec.localCodegenType.name) - addParameter(ParameterSpec.builder(name, codec.localCodegenType.name) - .defaultValue(defaultValue) - .build()) + addParameter( + ParameterSpec.builder(name, codec.localCodegenType.name) + .defaultValue(defaultValue) + .build() + ) } // BODY with(DownwardFunctionsCodegen) { @@ -101,8 +119,14 @@ private fun KneeDownwardFunction.makeCodegen(codegen: KneeCodegen, signature: Do if (signature.isSuspend) { // public suspend fun kneeInvokeJvmSuspend(block: (KneeSuspendInvoker) -> Long): T addCode(CodeBlock.builder().apply { - val invoke = MemberName("io.deepmedia.tools.knee.runtime.compiler", "kneeInvokeJvmSuspend") - beginControlFlow("val res = %M { ${DownwardFunctionSignature.Extra.SuspendInvoker} ->", invoke) + val invoke = MemberName( + "io.deepmedia.tools.knee.runtime.compiler", + "kneeInvokeJvmSuspend" + ) + beginControlFlow( + "val res = %M { ${DownwardFunctionSignature.Extra.SuspendInvoker} ->", + invoke + ) bridgeSpec = codegenInvoke(signature, bridgeName, "val token = ", codecContext) codegenReceive("token", signature, "", codecContext, suspendToken = true) endControlFlow() @@ -135,12 +159,17 @@ private fun KneeDownwardFunction.makeCodegen(codegen: KneeCodegen, signature: Do // TODO: use FunctionSignature.JniInfo.owner for at least one of this of these containers val localContainer = kind.property?.codegenImplementation ?: when (kind) { is Kind.InterfaceMember -> kind.owner.codegenImplementation - else -> codegen.prepareContainer(source, kind.importInfo) + else -> codegen.ensureContainer(source, kind.importInfo) } val bridgeContainer = when (kind) { // skip properties in this case is Kind.InterfaceMember -> kind.owner.codegenImplementation - is Kind.ClassConstructor -> codegen.prepareContainer(source, kind.importInfo, createCompanionObject = true) - else -> codegen.prepareContainer(source, kind.importInfo, detectPropertyAccessors = false) + is Kind.ClassConstructor -> codegen.ensureContainer( + source, + kind.importInfo, + createCompanionObject = true + ) + + else -> codegen.ensureContainer(source, kind.importInfo, detectPropertyAccessors = false) } localContainer.addChild(localFun) @@ -166,7 +195,11 @@ private fun KneeDownwardFunction.makeCodegen(codegen: KneeCodegen, signature: Do } } -private fun KneeDownwardFunction.makeIr(context: KneeContext, signature: DownwardFunctionSignature, initInfo: InitInfo) { +private fun KneeDownwardFunction.makeIr( + context: KneeContext, + signature: DownwardFunctionSignature, + initInfo: InitInfo +) { val file = kind.importInfo?.file ?: source.file val property = file.addSimpleProperty( plugin = context.plugin, @@ -183,7 +216,7 @@ private fun KneeDownwardFunction.makeIr(context: KneeContext, signature: Downwar } ) // only argument of staticCFunction is a lambda - staticCFunctionCall.putValueArgument(0, irLambda( + staticCFunctionCall.arguments[0] = irLambda( context = context, parent = parent, content = { lambda -> @@ -191,31 +224,48 @@ private fun KneeDownwardFunction.makeIr(context: KneeContext, signature: Downwar var args = 0 signature.knPrefixParameters.forEach { (name, type) -> lambda.addValueParameter(name, type, KneeOrigin.KNEE) - staticCFunctionCall.putTypeArgument(args++, type) + staticCFunctionCall.typeArguments[args++] = type } signature.extraParameters.forEach { (param, codec) -> val type = codec.encodedType.knOrNull!! lambda.addValueParameter(param, type) - staticCFunctionCall.putTypeArgument(args++, type) + staticCFunctionCall.typeArguments[args++] = type } signature.regularParameters.forEach { (param, codec) -> val type = codec.encodedType.knOrNull!! - val sourceParam = source.valueParameters.first { it.name == param } + val sourceParam = + source.parameters + .filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + .first { it.name == param } // defaultValue = null is very important here because we are changing the type, potentially - lambda.valueParameters += sourceParam.copyTo(lambda, index = args, type = type, name = param, defaultValue = null) - staticCFunctionCall.putTypeArgument(args++, type) + /** + * index will be solve with new compiler, + * + * @see [org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl.createValueParameter] + */ + lambda.parameters += sourceParam.copyTo( + lambda, + // index = args, + type = type, + name = param, + defaultValue = null + ) + staticCFunctionCall.typeArguments[args++] = type } run { - val resultOrSuspendResult = (if (signature.isSuspend) signature.suspendResult else signature.result) - .encodedType.knOrNull ?: context.symbols.builtIns.unitType + val resultOrSuspendResult = + (if (signature.isSuspend) signature.suspendResult else signature.result) + .encodedType.knOrNull ?: context.symbols.builtIns.unitType lambda.returnType = resultOrSuspendResult - staticCFunctionCall.putTypeArgument(args, resultOrSuspendResult) + staticCFunctionCall.typeArguments[args] = resultOrSuspendResult } // actual body where we call the user-defined function and do mapping val environment = - lambda.valueParameters.first { it.name == DownwardFunctionSignature.KnPrefix.JniEnvironment } + lambda.parameters + .filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + .first { it.name == DownwardFunctionSignature.KnPrefix.JniEnvironment } val codecContext = IrCodecContext( functionSymbol = source.symbol, environment = environment, @@ -224,86 +274,148 @@ private fun KneeDownwardFunction.makeIr(context: KneeContext, signature: Downwar ) val logPrefix = "Functions.kt(${source.fqNameWhenAvailable})" context.log.injectLog(this, "$logPrefix CALLED FROM JVM") - +irReturn(if (!signature.isSuspend) { - with(DownwardFunctionsIr) { - // val raw = irInvoke(lambda.valueParameters, source, signature, codecContext) - // irReceive(raw, signature, codecContext) - val catch = buildVariable(parent, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, IrDeclarationOrigin.CATCH_PARAMETER, Name.identifier("t"), context.symbols.builtIns.throwableType) - irTry( - type = signature.result.encodedType.knOrNull ?: context.symbols.builtIns.unitType, - tryResult = irComposite { - val raw = irInvoke(lambda.valueParameters, source, signature, codecContext) - +irReceive(raw, signature, codecContext) - }, - catches = listOf(irCatch( - catchParameter = catch, - result = irComposite { - // Forward the error to the JVM and swallow it on the native side. - +irCall(context.symbols.functions(rethrowNativeException).single()).apply { - extensionReceiver = irGet(environment) - putValueArgument(0, irGet(catch)) - } - // Return 'something' here otherwise compilation fails (I think). - // It will never be used anyway because the JVM will throw due to previous command. - +when (val type = signature.result.encodedType) { - is JniType.Void -> irUnit() - is JniType.Object -> irNull() - is JniType.Int -> irInt(0) - is JniType.Long -> irLong(0) - is JniType.Float -> IrConstImpl.float(startOffset, endOffset, type.kn, 0F) - is JniType.Double -> IrConstImpl.double(startOffset, endOffset, type.kn, 0.0) - is JniType.Byte -> IrConstImpl.byte(startOffset, endOffset, type.kn, 0) - is JniType.BooleanAsUByte -> IrConstImpl.byte(startOffset, endOffset, type.kn, 0) // hope this works... - } + +irReturn( + if (!signature.isSuspend) { + with(DownwardFunctionsIr) { + val target = importedAdapter ?: source + // val raw = irInvoke(lambda.valueParameters, source, signature, codecContext) + // irReceive(raw, signature, codecContext) + val catch = buildVariable( + parent, + SYNTHETIC_OFFSET, + SYNTHETIC_OFFSET, + IrDeclarationOrigin.CATCH_PARAMETER, + Name.identifier("t"), + context.symbols.builtIns.throwableType + ) + irTry( + type = signature.result.encodedType.knOrNull + ?: context.symbols.builtIns.unitType, + tryResult = irComposite { + val raw = irInvoke( + lambda.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }, + source, + target, + signature, + codecContext + ) + +irReceive(raw, signature, codecContext) + }, + catches = listOf( + irCatch( + catchParameter = catch, + result = irComposite { + // Forward the error to the JVM and swallow it on the native side. + +irCall( + context.symbols.functions(rethrowNativeException) + .single() + ).apply { + val extensionIndex = symbol.owner.parameters + .indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } + val throwableIndex = symbol.owner.parameters + .indexOfFirst { it.name.asString() == "throwable" } + arguments[extensionIndex] = irGet(environment) + arguments[throwableIndex] = irGet(catch) + } + // Return 'something' here otherwise compilation fails (I think). + // It will never be used anyway because the JVM will throw due to previous command. + +when (val type = signature.result.encodedType) { + is JniType.Void -> irUnit() + is JniType.Object -> irNull() + is JniType.Int -> irInt(0) + is JniType.Long -> irLong(0) + is JniType.Float -> IrConstImpl.float( + startOffset, + endOffset, + type.kn, + 0F + ) + + is JniType.Double -> IrConstImpl.double( + startOffset, + endOffset, + type.kn, + 0.0 + ) + + is JniType.Byte -> IrConstImpl.byte( + startOffset, + endOffset, + type.kn, + 0 + ) + + is JniType.BooleanAsUByte -> IrConstImpl.byte( + startOffset, + endOffset, + type.kn, + 0 + ) // hope this works... + } + } + )), + finallyExpression = null + ) + } + } else { + val suspendInvoke = context.symbols.functions(kneeInvokeJvmSuspend).single() + val suspendInvoker = + irGet(lambda.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + .first { it.name == DownwardFunctionSignature.Extra.SuspendInvoker }) + val returnType = signature.result + irCall(suspendInvoke.owner).apply { + + typeArguments[0] = + returnType.encodedType.knOrNull + ?: context.symbols.builtIns.unitType // raw return type + typeArguments[1] = returnType.localIrType // actual return type + arguments[0] = irGet(environment) + arguments[1] = suspendInvoker + arguments[2] = irLambda(context, parent, suspend = true) { + it.returnType = returnType.localIrType + with(DownwardFunctionsIr) { + +irReturn( + irInvoke( + lambda.parameters + .filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }, + source, + importedAdapter ?: source, + signature, + codecContext + ) + ) } - )), - finallyExpression = null - ) - } - } else { - val suspendInvoke = context.symbols.functions(kneeInvokeJvmSuspend).single() - val suspendInvoker = - irGet(lambda.valueParameters.first { it.name == DownwardFunctionSignature.Extra.SuspendInvoker }) - val returnType = signature.result - irCall(suspendInvoke.owner).apply { - putTypeArgument(0, returnType.encodedType.knOrNull ?: context.symbols.builtIns.unitType) // raw return type - putTypeArgument(1, returnType.localIrType) // actual return type - putValueArgument(0, irGet(environment)) - putValueArgument(1, suspendInvoker) - putValueArgument(2, irLambda(context, parent, suspend = true) { - it.returnType = returnType.localIrType - with(DownwardFunctionsIr) { - +irReturn(irInvoke(lambda.valueParameters, source, signature, codecContext)) } - }) - putValueArgument(3, irLambda(context, parent) { - it.returnType = returnType.encodedType.knOrNull ?: context.symbols.builtIns.unitType - it.addValueParameter("_env", environment.type) - it.addValueParameter("_data", returnType.localIrType) - // Need a new context because the local invocation might have suspended and might have returned - // on another thread with no current environment. This is also why we have two lambdas here, so that the - // new environment is provided by the runtime. - val freshCodecContext = IrCodecContext( - functionSymbol = source.symbol, - environment = it.valueParameters[0], - reverse = false, - logger = context.log - ) - val raw = irGet(it.valueParameters[1]) + arguments[3] = irLambda(context, parent) { + it.returnType = returnType.encodedType.knOrNull + ?: context.symbols.builtIns.unitType + it.addValueParameter("_env", environment.type) + it.addValueParameter("_data", returnType.localIrType) + // Need a new context because the local invocation might have suspended and might have returned + // on another thread with no current environment. This is also why we have two lambdas here, so that the + // new environment is provided by the runtime. + val freshCodecContext = IrCodecContext( + functionSymbol = source.symbol, + environment = it.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[0], + reverse = false, + logger = context.log + ) + val raw = + irGet(it.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[1]) + with(DownwardFunctionsIr) { + +irReturn(irReceive(raw, signature, freshCodecContext)) + } + } + }.let { call -> + // Technically this is useless, token is a long and needs to conversion, but leaving it + // for clarity and future-proofness. with(DownwardFunctionsIr) { - +irReturn(irReceive(raw, signature, freshCodecContext)) + irReceive(call, signature, codecContext, suspendToken = true) } - }) - }.let { call -> - // Technically this is useless, token is a long and needs to conversion, but leaving it - // for clarity and future-proofness. - with(DownwardFunctionsIr) { - irReceive(call, signature, codecContext, suspendToken = true) } - } - }) + }) } - )) + ) staticCFunctionCall } irProducts.add(property) diff --git a/knee-compiler-plugin/src/main/kotlin/DownwardProperties.kt b/knee-compiler-plugin/src/main/kotlin/DownwardProperties.kt index 1de2bf0..b6661d2 100644 --- a/knee-compiler-plugin/src/main/kotlin/DownwardProperties.kt +++ b/knee-compiler-plugin/src/main/kotlin/DownwardProperties.kt @@ -51,17 +51,17 @@ private fun KneeDownwardProperty.makeCodegen(codegen: KneeCodegen, symbols: Knee is KneeDownwardProperty.Kind.ClassMember -> { val isOverride = kind.owner.isOverrideInCodegen(symbols, this) val property = makeProperty(isOverride = isOverride) - codegen.prepareContainer(source, kind.importInfo).addChild(property) + codegen.ensureContainer(source, kind.importInfo).addChild(property) codegenImplementation = property } is KneeDownwardProperty.Kind.ObjectMember -> { val property = makeProperty(isOverride = false) - codegen.prepareContainer(source, kind.importInfo).addChild(property) + codegen.ensureContainer(source, kind.importInfo).addChild(property) codegenImplementation = property } is KneeDownwardProperty.Kind.TopLevel -> { val property = makeProperty() - codegen.prepareContainer(source, kind.importInfo).addChild(property) + codegen.ensureContainer(source, kind.importInfo).addChild(property) codegenImplementation = property } } diff --git a/knee-compiler-plugin/src/main/kotlin/Enums.kt b/knee-compiler-plugin/src/main/kotlin/Enums.kt index 246dcef..6b357c4 100644 --- a/knee-compiler-plugin/src/main/kotlin/Enums.kt +++ b/knee-compiler-plugin/src/main/kotlin/Enums.kt @@ -50,7 +50,7 @@ fun KneeEnum.makeCodegenClone(codegen: KneeCodegen) { } CodegenClass(this) } - codegen.prepareContainer(source, importInfo).addChild(clone) + codegen.ensureContainer(source, importInfo).addChild(clone) codegenProducts.add(clone) } @@ -72,25 +72,37 @@ class EnumCodec( private val encode = symbols.functions(encodeEnum).single() private val decode = symbols.functions(decodeEnum).single() - override fun IrStatementsBuilder<*>.irDecode(irContext: IrCodecContext, jni: IrValueDeclaration): IrExpression { + override fun IrStatementsBuilder<*>.irDecode( + irContext: IrCodecContext, + jni: IrValueDeclaration + ): IrExpression { return irCall(decode).apply { - putTypeArgument(0, localIrType) - putValueArgument(0, irGet(jni)) + typeArguments[0] = localIrType + arguments[0] = irGet(jni) } } - override fun IrStatementsBuilder<*>.irEncode(irContext: IrCodecContext, local: IrValueDeclaration): IrExpression { + override fun IrStatementsBuilder<*>.irEncode( + irContext: IrCodecContext, + local: IrValueDeclaration + ): IrExpression { return irCall(encode).apply { - putTypeArgument(0, localIrType) - putValueArgument(0, irGet(local)) + typeArguments[0] = localIrType + arguments[0] = irGet(local) } } - override fun CodeBlock.Builder.codegenDecode(codegenContext: CodegenCodecContext, jni: String): String { + override fun CodeBlock.Builder.codegenDecode( + codegenContext: CodegenCodecContext, + jni: String + ): String { return "kotlin.enums.enumEntries<${localCodegenType.name}>()[$jni]" } - override fun CodeBlock.Builder.codegenEncode(codegenContext: CodegenCodecContext, local: String): String { + override fun CodeBlock.Builder.codegenEncode( + codegenContext: CodegenCodecContext, + local: String + ): String { return "$local.ordinal" } } \ No newline at end of file diff --git a/knee-compiler-plugin/src/main/kotlin/Init.kt b/knee-compiler-plugin/src/main/kotlin/Init.kt index 5146c80..162959f 100644 --- a/knee-compiler-plugin/src/main/kotlin/Init.kt +++ b/knee-compiler-plugin/src/main/kotlin/Init.kt @@ -23,12 +23,13 @@ import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrConstructor +import org.jetbrains.kotlin.ir.declarations.IrParameterKind import org.jetbrains.kotlin.ir.declarations.IrProperty import org.jetbrains.kotlin.ir.expressions.* 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.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.IrVisitorVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid @@ -62,7 +63,8 @@ fun processInit( // 4. write dependency information in the module metadata (via annotation) // https://github.com/androidx/androidx/blob/fec3b387ce47bad7682d01042c22d1913268c2bc/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerIntrinsicTransformer.kt#L62 val exportedTypes = mutableListOf() - val dependencyModules = module.collectDependencies() // do before transforming the super constructor! + val dependencyModules = + module.collectDependencies() // do before transforming the super constructor! module.source.transformChildrenVoid(object : IrElementTransformerVoid() { // Grab the superclass constructor call. Will call visitDelegatingConstructorCall @@ -72,15 +74,16 @@ fun processInit( } override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression { - val isPublicConstructor = expression.symbol == context.symbols.modulePublicConstructor.symbol + val isPublicConstructor = + expression.symbol == context.symbols.modulePublicConstructor.symbol if (isPublicConstructor) { val builder = DeclarationIrBuilder(context.plugin, expression.symbol) return builder.irCreateModule( isSuperclass = true, symbols = context.symbols, initInfo = info, - varargDependencies = expression.getValueArgument(0), - builderBlock = expression.getValueArgument(1) + varargDependencies = expression.arguments[0], + builderBlock = expression.arguments[1] ) } return super.visitDelegatingConstructorCall(expression) @@ -88,11 +91,19 @@ fun processInit( override fun visitCall(expression: IrCall): IrExpression { if (context.useExport2 && expression.symbol == context.symbols.moduleBuilderExportFunction) { - val exportedType = expression.getTypeArgument(0)!!.simple("export()") - val exportedTypeInfo = ExportedTypeInfo(exportedTypes.size, context.mapper.get(exportedType)) + val exportedType = + expression.typeArguments[0]!!.simple("export()") + val exportedTypeInfo = ExportedTypeInfo( + exportedTypes.size, + context.mapper.get(exportedType) + ) exportedTypes.add(exportedTypeInfo) val builder = DeclarationIrBuilder(context.plugin, module.source.symbol) - val replacement = builder.irExportAdapter(context, expression.dispatchReceiver!!, exportedTypeInfo) + val replacement = builder.irExportAdapter( + context, + expression.dispatchReceiver!!, + exportedTypeInfo + ) //println("ORIGINAL MODULE_BUILDER_EXPORT = ${expression.dumpKotlinLike()}") //println("REPLACEMENT MODULE_BUILDER_EXPORT = ${replacement.dumpKotlinLike()}") return replacement @@ -111,6 +122,7 @@ fun processInit( codegen.makeCodegenModule(module, context, exportedTypes) } } + is InitInfo.Initializer -> { // It's possible to have multiple initKnee() call, for example in a if-else branch. // We don't care, process all of them and inject a synthetic module @@ -118,17 +130,19 @@ fun processInit( // Goal: replace initKnee(ENV, dep1, dep2, dep3, ...) with initKnee(ENV, SyntheticModule(dep1, dep2, dep3)) // TODO: it is wrong to pass the expression symbol, it represents the initKnee() function in runtime module val builder = DeclarationIrBuilder(context.plugin, initializer.expression.symbol) - val dependencies = initializer.expression.getValueArgument(1) - initializer.expression.putValueArgument(1, builder.irVararg( + val dependencies = initializer.expression.arguments[1] + initializer.expression.arguments[1] = builder.irVararg( elementType = context.symbols.moduleClass.defaultType, - values = listOf(builder.irCreateModule( - isSuperclass = false, - symbols = context.symbols, - initInfo = info, - varargDependencies = dependencies, - builderBlock = null - )) - )) + values = listOf( + builder.irCreateModule( + isSuperclass = false, + symbols = context.symbols, + initInfo = info, + varargDependencies = dependencies, + builderBlock = null + ) + ) + ) } } } @@ -166,7 +180,14 @@ sealed class InitInfo { methodJniSignature: String ) { context.log.logMessage("registerNative: adding $methodName ($methodJniSignature) in ${container.jvmClassName}") - registerNativesEntries.add(RegisterNativesEntry(container, pointerProperty, methodName, methodJniSignature)) + registerNativesEntries.add( + RegisterNativesEntry( + container, + pointerProperty, + methodName, + methodJniSignature + ) + ) } data class RegisterNativesEntry( @@ -205,20 +226,20 @@ private fun DeclarationIrBuilder.irCreateModule( // val registerNativeContainers: List // val registerNativeMethods: List> val groups = initInfo.registerNativesEntries.groupBy { it.container }.entries.map { it } - putValueArgument(0, irRegisterNativesContainers(symbols, groups.map { it.key })) - putValueArgument(1, irRegisterNativesMethods(symbols, groups.map { it.value })) + arguments[0] = irRegisterNativesContainers(symbols, groups.map { it.key }) + arguments[1] = irRegisterNativesMethods(symbols, groups.map { it.value }) // val preloadFqns: List - putValueArgument(2, irPreloadFqns(symbols, initInfo.preloads)) + arguments[2] = irPreloadFqns(symbols, initInfo.preloads) // val exceptions: List - putValueArgument(3, irSerializableExceptions(symbols, initInfo.serializableExceptions)) + arguments[3] = irSerializableExceptions(symbols, initInfo.serializableExceptions) // val dependencies: List // val block: (KneeModuleBuilder.() -> Unit)? val dependencies = varargDependencies.varargElements() - putValueArgument(4, irListOf(symbols, symbols.moduleClass.defaultType, dependencies)) - putValueArgument(5, builderBlock ?: irNull()) + arguments[4] = irListOf(symbols, symbols.moduleClass.defaultType, dependencies) + arguments[5] = builderBlock ?: irNull() // val dependencies: Array? // val block: (KneeModuleBuilder.() -> Unit)? @@ -229,48 +250,74 @@ private fun DeclarationIrBuilder.irCreateModule( return constructorCall } -private fun DeclarationIrBuilder.irListOf(symbols: KneeSymbols, type: IrType, contents: List): IrExpression { - val listOf = symbols.functions(KotlinIds.listOf).single { it.owner.valueParameters.singleOrNull()?.isVararg == true } +private fun DeclarationIrBuilder.irListOf( + symbols: KneeSymbols, + type: IrType, + contents: List +): IrExpression { + val listOf = symbols.functions(KotlinIds.listOf) + .single { + it.owner.parameters + .singleOrNull { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + ?.isVararg == true + } return irCall(listOf).apply { - putTypeArgument(0, type) - putValueArgument(0, irVararg(type, contents)) + typeArguments[0] = type + arguments[0] = irVararg(type, contents) } } -private fun DeclarationIrBuilder.irPreloadFqns(symbols: KneeSymbols, preloads: Set): IrExpression { +private fun DeclarationIrBuilder.irPreloadFqns( + symbols: KneeSymbols, + preloads: Set +): IrExpression { return irListOf(symbols, symbols.builtIns.stringType, preloads.map { irString(CodegenType.from(it).jvmClassName) }) } -private fun DeclarationIrBuilder.irSerializableExceptions(symbols: KneeSymbols, classes: Set): IrExpression { +private fun DeclarationIrBuilder.irSerializableExceptions( + symbols: KneeSymbols, + classes: Set +): IrExpression { val type = symbols.klass(RuntimeIds.SerializableException) return irListOf(symbols, type.defaultType, classes.map { irCallConstructor(type.constructors.single(), emptyList()).apply { - putValueArgument(0, irString(it.classIdOrFail.asFqNameString())) // nativeFqn: String - putValueArgument(1, irString(CodegenType.from(it.defaultType).jvmClassName)) // jvmFqn: String + arguments[0] = irString(it.classIdOrFail.asFqNameString()) // nativeFqn: String + arguments[1] = irString(it.classIdOrFail.asFqNameString()) // jvmFqn: String } }) } -private fun DeclarationIrBuilder.irRegisterNativesContainers(symbols: KneeSymbols, containers: List): IrExpression { - return irListOf(symbols, symbols.builtIns.stringType, containers.map { irString(it.jvmClassName) }) +private fun DeclarationIrBuilder.irRegisterNativesContainers( + symbols: KneeSymbols, + containers: List +): IrExpression { + return irListOf( + symbols, + symbols.builtIns.stringType, + containers.map { irString(it.jvmClassName) }) } -private fun DeclarationIrBuilder.irRegisterNativesMethods(symbols: KneeSymbols, entriesLists: List>): IrExpression { +private fun DeclarationIrBuilder.irRegisterNativesMethods( + symbols: KneeSymbols, + entriesLists: List> +): IrExpression { val methodClass = symbols.klass(JNINativeMethod) val methodConstructor = methodClass.constructors.single() val listOfMethods = symbols.builtIns.listClass.typeWith(methodClass.defaultType) - return irListOf(symbols, + return irListOf( + symbols, type = symbols.builtIns.listClass.typeWith(listOfMethods), contents = entriesLists.map { entries -> - irListOf(symbols, + irListOf( + symbols, type = listOfMethods, contents = entries.map { entry -> irCallConstructor(methodConstructor, emptyList()).apply { - putValueArgument(0, irString(entry.methodName)) - putValueArgument(1, irString(entry.methodJniSignature)) - putValueArgument(2, irCall(entry.pointerProperty.getter!!)) + arguments[0] = irString(entry.methodName) + arguments[1] = irString(entry.methodJniSignature) + arguments[2] = irCall(entry.pointerProperty.getter!!) } } ) @@ -286,11 +333,11 @@ private fun DeclarationIrBuilder.irExportAdapter( val function = context.symbols.moduleBuilderExportAdapterFunction return irCall(function).apply { dispatchReceiver = moduleBuilderInstance - putTypeArgument(0, exportedType.encodedType.kn) - putTypeArgument(1, exportedType.localIrType) + typeArguments[0] = exportedType.encodedType.kn + typeArguments[1] = exportedType.localIrType // dispatchReceiver = irGet(function.owner.parentAsClass.thisReceiver!!) - putValueArgument(0, irInt(exportedType.id)) - putValueArgument(1, with(ExportAdapters2) { irCreateExportAdapter(exportedType, context) }) + arguments[0] = irInt(exportedType.id) + arguments[1] = with(ExportAdapters2) { irCreateExportAdapter(exportedType, context) } } } @@ -300,14 +347,15 @@ private fun DeclarationIrBuilder.irExportAdapter( */ private fun KneeModule.collectDependencies(): List { var expr: IrExpression? = null - source.constructors.single().body!!.acceptChildrenVoid(object : IrElementVisitorVoid { + source.constructors.single().body!!.acceptChildrenVoid(object : IrVisitorVoid() { override fun visitElement(element: IrElement) { element.acceptChildrenVoid(this) } + override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall) { check(expression.symbol.owner.constructedClass.classId == RuntimeIds.KneeModule) { "Wrong delegating constructor call: ${expression.dumpKotlinLike()}" } - check(expr == null) { "Found two delegating constructor call: ${expr}, ${expression}"} - expr = expression.getValueArgument(0) + check(expr == null) { "Found two delegating constructor call: ${expr}, ${expression}" } + expr = expression.arguments[0] super.visitDelegatingConstructorCall(expression) } }) @@ -319,41 +367,59 @@ private fun KneeModule.collectDependencies(): List { * We just need to retrieve and unwrap the second argument.. */ private fun KneeInitializer.collectDependencies(): List { - return expression.getValueArgument(1).varargElements().map { it.symbol.owner } + return expression.arguments[1].varargElements().map { it.symbol.owner } } /** * Vararg expressions can sometimes be null, if no items were provided. */ -private inline fun IrExpression?.varargElements(): List { +private inline fun IrExpression?.varargElements(): List { if (this == null) return emptyList() - return (this as IrVararg).elements.map { it as? T ?: error("Vararg elements should be ${T::class}, was ${it::class}") } + val irVararg = this as? IrVararg ?: error("Expected IrVararg but got ${this::class.simpleName}. Make sure you are not using spread operators (*) in knee functions.") + return irVararg.elements.map { + it as? T ?: error("Vararg elements should be ${T::class}, was ${it::class}") + } } -private fun KneeCodegen.makeCodegenModule(module: KneeModule, context: KneeContext, exportedTypes: List) { +private fun KneeCodegen.makeCodegenModule( + module: KneeModule, + context: KneeContext, + exportedTypes: List +) { val name = module.source.name.asString() - val container = prepareContainer(module.source, null) - val moduleClass: ClassName = context.symbols.klass(RuntimeIds.KneeModule).owner.defaultType.asTypeName() as ClassName + val container = ensureContainer(module.source, null) + val moduleClass: ClassName = + context.symbols.klass(RuntimeIds.KneeModule).owner.defaultType.asTypeName() as ClassName val adapterClass: ClassName = moduleClass.nestedClass("Adapter") val builder = TypeSpec.objectBuilder(name) .addModifiers(KModifier.PUBLIC) .let { if (verbose) it.addKdoc("knee:init") else it } .superclass(context.symbols.klass(RuntimeIds.KneeModule).owner.defaultType.asTypeName()) .addProperty( - PropertySpec.builder("exportAdapters", MAP.parameterizedBy(INT, adapterClass.parameterizedBy(STAR, STAR)), KModifier.OVERRIDE) - .initializer(CodeBlock.builder() - .addStatement("mapOf(") - .withIndent { - for (exportedType in exportedTypes) { - add("${exportedType.id} to ") - add(CodeBlock.builder().apply { - with(ExportAdapters2) { codegenCreateExportAdapter(exportedType, context) } - }.build()) - add(", ") + PropertySpec.builder( + "exportAdapters", + MAP.parameterizedBy(INT, adapterClass.parameterizedBy(STAR, STAR)), + KModifier.OVERRIDE + ) + .initializer( + CodeBlock.builder() + .addStatement("mapOf(") + .withIndent { + for (exportedType in exportedTypes) { + add("${exportedType.id} to ") + add(CodeBlock.builder().apply { + with(ExportAdapters2) { + codegenCreateExportAdapter( + exportedType, + context + ) + } + }.build()) + add(", ") + } } - } - .addStatement(")") - .build() + .addStatement(")") + .build() ) .build() ) diff --git a/knee-compiler-plugin/src/main/kotlin/Interfaces.kt b/knee-compiler-plugin/src/main/kotlin/Interfaces.kt index a150c31..da6e3c2 100644 --- a/knee-compiler-plugin/src/main/kotlin/Interfaces.kt +++ b/knee-compiler-plugin/src/main/kotlin/Interfaces.kt @@ -45,48 +45,80 @@ import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.types.KotlinType fun preprocessInterface(interface_: KneeInterface, context: KneeContext) { - context.log.logMessage("preprocessInterface(${interface_.source.name}), owned = ${interface_.source.isPartOf(context.module)}") + context.log.logMessage( + "preprocessInterface(${interface_.source.name}), owned = ${ + interface_.source.isPartOf( + context.module + ) + }" + ) interface_.makeIrImplementation(context) - context.mapper.register(InterfaceCodec( - context = context, - interfaceClass = interface_.source, - interfaceImplClass = interface_.irImplementation, - importInfo = interface_.importInfo - )) + context.mapper.register( + InterfaceCodec( + context = context, + interfaceClass = interface_.source, + interfaceImplClass = interface_.irImplementation, + importInfo = interface_.importInfo + ) + ) } -fun processInterface(interface_: KneeInterface, context: KneeContext, codegen: KneeCodegen, initInfo: InitInfo) { - context.log.logMessage("processInterface(${interface_.source.name}), owned = ${interface_.source.isPartOf(context.module)}") +fun processInterface( + interface_: KneeInterface, + context: KneeContext, + codegen: KneeCodegen, + initInfo: InitInfo +) { + context.log.logMessage( + "processInterface(${interface_.source.name}), owned = ${ + interface_.source.isPartOf( + context.module + ) + }" + ) if (interface_.source.isPartOf(context.module)) { interface_.makeCodegenClone(codegen) } interface_.makeCodegenImplementation(codegen, context) interface_.makeIrImplementationContents(context) // Generics should not matter here because we just findClass() the FQN - initInfo.preload(listOf( - interface_.source.defaultType, - interface_.irImplementation.defaultType - )) + initInfo.preload( + listOf( + interface_.source.defaultType, + interface_.irImplementation.defaultType + ) + ) run { // Trick so that we don't have to pass the dispatch receiver from the function we are building. // This is not 100% safe, it would probably fail in nested scopes e.g. inside irLambda. - fun IrBuilderWithScope.irThis() = irGet((scope.scopeOwnerSymbol as IrSimpleFunctionSymbol).owner.dispatchReceiverParameter!!) + fun IrBuilderWithScope.irThis() = + irGet((scope.scopeOwnerSymbol as IrSimpleFunctionSymbol).owner.dispatchReceiverParameter!!) val utilitySuperClass = context.symbols.klass(JvmInterfaceWrapper).owner - val virtualMachine = utilitySuperClass.findDeclaration { it.name.asString() == "virtualMachine" }!!.getter!! - val methodOwner = utilitySuperClass.findDeclaration { it.name.asString() == "methodOwnerClass" }!!.getter!! - val jvmInterfaceObject = utilitySuperClass.findDeclaration { it.name.asString() == "jvmInterfaceObject" }!!.getter!! - val methodFromSignature = utilitySuperClass.findDeclaration { it.name.asString() == "method" }!! + val virtualMachine = + utilitySuperClass.findDeclaration { it.name.asString() == "virtualMachine" }!!.getter!! + val methodOwner = + utilitySuperClass.findDeclaration { it.name.asString() == "methodOwnerClass" }!!.getter!! + val jvmInterfaceObject = + utilitySuperClass.findDeclaration { it.name.asString() == "jvmInterfaceObject" }!!.getter!! + val methodFromSignature = + utilitySuperClass.findDeclaration { it.name.asString() == "method" }!! - interface_.irGetVirtualMachine = { irCall(virtualMachine).apply { dispatchReceiver = irThis() }} - interface_.irGetMethodOwner = { irCall(methodOwner).apply { dispatchReceiver = irThis() }} - interface_.irGetJvmObject = { irCall(jvmInterfaceObject).apply { dispatchReceiver = irThis() }} - interface_.irGetMethod = { signature -> irCall(methodFromSignature).apply { - dispatchReceiver = irThis() - putValueArgument(0, irString(signature.jniInfo.name(false).asString() + "::" + signature.jniInfo.signature)) - }} + interface_.irGetVirtualMachine = + { irCall(virtualMachine).apply { dispatchReceiver = irThis() } } + interface_.irGetMethodOwner = { irCall(methodOwner).apply { dispatchReceiver = irThis() } } + interface_.irGetJvmObject = + { irCall(jvmInterfaceObject).apply { dispatchReceiver = irThis() } } + interface_.irGetMethod = { signature -> + irCall(methodFromSignature).apply { + dispatchReceiver = irThis() + arguments[methodFromSignature.parameters.indexOfFirst { it.name.asString() == "key" }] = irString( + signature.jniInfo.name(false).asString() + "::" + signature.jniInfo.signature + ) + } + } } if (!context.useExport2) { @@ -102,7 +134,7 @@ fun processInterface(interface_: KneeInterface, context: KneeContext, codegen: K * We use addChildIfNeeded for this. */ private fun KneeInterface.makeCodegenClone(codegen: KneeCodegen) { - val container = codegen.prepareContainer(source, importInfo) + val container = codegen.ensureContainer(source, importInfo) val builder = when { source.isFun -> TypeSpec.funInterfaceBuilder(source.name.asString()) else -> TypeSpec.interfaceBuilder(source.name.asString()) @@ -123,7 +155,7 @@ private fun KneeInterface.makeCodegenClone(codegen: KneeCodegen) { private fun KneeInterface.makeCodegenImplementation(codegen: KneeCodegen, context: KneeContext) { val name = source.codegenName.asInterfaceName(importInfo).asString() val exported1 = !context.useExport2 && source.hasExport1Flag - val container = codegen.prepareContainer(source, importInfo) + val container = codegen.ensureContainer(source, importInfo) val builder = TypeSpec.classBuilder(name).apply { when { exported1 -> addModifiers(KModifier.PUBLIC) @@ -156,8 +188,9 @@ private fun KneeInterface.makeIrImplementation(context: KneeContext) { this.name = source.name.asInterfaceName(importInfo) }.also { wrapperClass -> wrapperClass.parent = container - wrapperClass.superTypes = listOf(sourceConcreteType, superClass.typeWith(sourceConcreteType)) - wrapperClass.createParameterDeclarations() // receiver + wrapperClass.superTypes = + listOf(sourceConcreteType, superClass.typeWith(sourceConcreteType)) + wrapperClass.createThisReceiverParameter() // receiver } container.addChild(wrapperClass) irProducts.add(wrapperClass) @@ -172,8 +205,14 @@ private fun KneeInterface.makeIrImplementationContents(context: KneeContext) { this.isPrimary = true }.let { constructor -> val superConstructor = superClass.primaryConstructor!! - constructor.valueParameters += superConstructor.valueParameters[0].copyTo(constructor, defaultValue = null) // 0: JniEnvironment - constructor.valueParameters += superConstructor.valueParameters[1].copyTo(constructor, defaultValue = null) // 1: jobject + superConstructor.parameters + .filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[0] + .copyTo(constructor, defaultValue = null) // 0: JniEnvironment + .let { constructor.parameters += it } + superConstructor.parameters + .filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[1] + .copyTo(constructor, defaultValue = null) // 1: jobject + .let { constructor.parameters += it } constructor.body = with(DeclarationIrBuilder(context.plugin, constructor.symbol)) { irBlockBody { context.log.injectLog(this, "Calling super constructor") @@ -181,28 +220,42 @@ private fun KneeInterface.makeIrImplementationContents(context: KneeContext) { // context.log.injectLog(this, CodegenType.from(irImplementation.defaultType).jvmClassName) +irDelegatingConstructorCall(superConstructor).apply { - putValueArgument(0, irGet(constructor.valueParameters[0])) - putValueArgument(1, irGet(constructor.valueParameters[1])) + arguments[0] = + irGet(constructor.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[0]) + arguments[1] = + irGet(constructor.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[1]) // Class FQNs will be passed to jni.findClass, so handle dollar sign and codegen renames correctly - putValueArgument(2, irString(CodegenType.from(sourceConcreteType).jvmClassName)) - putValueArgument(3, irString(CodegenType.from(irImplementation.defaultType).jvmClassName)) + arguments[2] = irString(CodegenType.from(sourceConcreteType).jvmClassName) + arguments[3] = + irString(CodegenType.from(irImplementation.defaultType).jvmClassName) + // Name and signature of the companion object function, alternated val allExportedFunctions = upwardFunctions + upwardProperties.mapNotNull(KneeUpwardProperty::setter) + upwardProperties.map(KneeUpwardProperty::getter) - putValueArgument(4, irVararg( + arguments[4] = irVararg( elementType = context.symbols.builtIns.stringType, values = allExportedFunctions.flatMap { - val signature = UpwardFunctionSignature(it.source, it.kind, context.symbols, context.mapper) + val signature = UpwardFunctionSignature( + it.source, + it.kind, + context.symbols, + context.mapper + ) listOf( irString(signature.jniInfo.name(false).asString()), irString(signature.jniInfo.signature) ) } - )) + ) } context.log.injectLog(this, "Called super constructor, init self") - +IrInstanceInitializerCallImpl(startOffset, endOffset, irImplementation.symbol, context.symbols.builtIns.unitType) + +IrInstanceInitializerCallImpl( + startOffset, + endOffset, + irImplementation.symbol, + context.symbols.builtIns.unitType + ) } } } @@ -251,11 +304,14 @@ class InterfaceCodec( * - if interface was originally JVM, return JVM! * - otherwise create a KN StableRef and return it as encoded long */ - override fun IrStatementsBuilder<*>.irEncode(irContext: IrCodecContext, local: IrValueDeclaration): IrExpression { + override fun IrStatementsBuilder<*>.irEncode( + irContext: IrCodecContext, + local: IrValueDeclaration + ): IrExpression { return irCall(encode).apply { - putTypeArgument(0, localIrType) - putValueArgument(0, irGet(irContext.environment)) - putValueArgument(1, irGet(local)) + typeArguments[0] = localIrType + arguments[0] = irGet(irContext.environment) + arguments[1] = irGet(local) } } @@ -265,28 +321,38 @@ class InterfaceCodec( * - otherwise it's a jobject with a reference to a JVM interface. * In this case we should create a FooImpl instance using the generated impl class. */ - override fun IrStatementsBuilder<*>.irDecode(irContext: IrCodecContext, jni: IrValueDeclaration): IrExpression { + override fun IrStatementsBuilder<*>.irDecode( + irContext: IrCodecContext, + jni: IrValueDeclaration + ): IrExpression { val logPrefix = "InterfaceCodec(${localCodegenType.name.simpleName})" irContext.logger.injectLog(this, "$logPrefix DECODING") return irCall(decode).apply { - putTypeArgument(0, localIrType) - putValueArgument(0, irGet(irContext.environment)) - putValueArgument(1, irGet(jni)) - putValueArgument(2, irLambda( + typeArguments[0] = localIrType + arguments[0] = irGet(irContext.environment) + arguments[1] = irGet(jni) + arguments[2] = irLambda( context = this@InterfaceCodec.context, parent = parent, valueParameters = emptyList(), returnType = interfaceImplClass.defaultType, content = { - irContext.logger.injectLog(this, "$logPrefix INSTANTIATING the implementation class") + irContext.logger.injectLog( + this, + "$logPrefix INSTANTIATING the implementation class" + ) // irContext.logger.injectLog(this, irContext.environment) // irContext.logger.injectLog(this, jni) - +irReturn(irCallConstructor(interfaceImplClass.primaryConstructor!!.symbol, emptyList()).apply { - putValueArgument(0, irGet(irContext.environment)) // environment - putValueArgument(1, irGet(jni)) // jobject - }) + +irReturn( + irCallConstructor( + interfaceImplClass.primaryConstructor!!.symbol, + emptyList() + ).apply { + arguments[0] = irGet(irContext.environment) // environment + arguments[1] = irGet(jni) // jobject + }) } - )) + ) } } @@ -296,7 +362,10 @@ class InterfaceCodec( * In this case we should create an instance of "KneeFoo" passing the address to the constructor * - An actual interface. This happens if the interface was originally created in Java. */ - override fun CodeBlock.Builder.codegenDecode(codegenContext: CodegenCodecContext, jni: String): String { + override fun CodeBlock.Builder.codegenDecode( + codegenContext: CodegenCodecContext, + jni: String + ): String { val fqn = localCodegenType.name val impl = interfaceImplClass.defaultType.asTypeName() /* val impl = fqn @@ -311,13 +380,19 @@ class InterfaceCodec( return "${jni}_" } - override fun CodeBlock.Builder.codegenEncode(codegenContext: CodegenCodecContext, local: String): String { + override fun CodeBlock.Builder.codegenEncode( + codegenContext: CodegenCodecContext, + local: String + ): String { // Special case during JVM to KN functions when the interface is the receiver. // It's not fundamental but avoids some warnings in generated code (this is Type where this is obviously type) if (local == "this") return "$local.`${InstancesCodegen.HandleField}`" val impl = interfaceImplClass.defaultType.asTypeName() - addStatement("val ${local}_: Any = ($local as? %T)?.`${InstancesCodegen.HandleField}` ?: $local", impl) + addStatement( + "val ${local}_: Any = ($local as? %T)?.`${InstancesCodegen.HandleField}` ?: $local", + impl + ) return "${local}_" } -} \ No newline at end of file +} diff --git a/knee-compiler-plugin/src/main/kotlin/Objects.kt b/knee-compiler-plugin/src/main/kotlin/Objects.kt index 62b3ff0..ab68e64 100644 --- a/knee-compiler-plugin/src/main/kotlin/Objects.kt +++ b/knee-compiler-plugin/src/main/kotlin/Objects.kt @@ -32,7 +32,7 @@ fun processObject(klass: KneeObject, context: KneeContext, codegen: KneeCodegen) } private fun KneeObject.makeCodegen(codegen: KneeCodegen) { - val container = codegen.prepareContainer(source, importInfo) + val container = codegen.ensureContainer(source, importInfo) codegenClone = container.addChildIfNeeded(CodegenClass(source.asTypeSpec())).apply { if (codegen.verbose) spec.addKdoc("knee:objects") spec.addModifiers(source.visibility.asModifier()) diff --git a/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt b/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt index 6ad1e13..dc34c7f 100644 --- a/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt +++ b/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt @@ -13,6 +13,7 @@ import io.deepmedia.tools.knee.plugin.compiler.context.KneeLogger import io.deepmedia.tools.knee.plugin.compiler.utils.* import io.deepmedia.tools.knee.plugin.compiler.symbols.CInteropIds import io.deepmedia.tools.knee.plugin.compiler.symbols.PlatformIds +import io.deepmedia.tools.knee.plugin.compiler.symbols.RuntimeIds.kneeGlobalize import io.deepmedia.tools.knee.plugin.compiler.symbols.RuntimeIds.kneeInvokeKnSuspend import io.deepmedia.tools.knee.plugin.compiler.symbols.RuntimeIds.useEnv import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder @@ -21,6 +22,7 @@ import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.builders.declarations.addFunction import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.types.IrSimpleType import org.jetbrains.kotlin.ir.types.typeWith import org.jetbrains.kotlin.ir.util.* @@ -55,6 +57,7 @@ private fun KneeUpwardFunction.makeCodegen( val spec = FunSpec .builder(signature.jniInfo.name(includeAncestors = false).asString()) .addModifiers(KModifier.PRIVATE) + // Only members in named objects and companion objects can be annotated with '@JvmStatic'. .addAnnotation(ClassName.bestGuess("kotlin.jvm.JvmStatic")) .returns((if (signature.isSuspend) signature.suspendResult else signature.result).encodedType.jvmOrNull?.name ?: UNIT) @@ -94,7 +97,7 @@ private fun KneeUpwardFunction.makeCodegen( // Save if (codegen.verbose) spec.addKdoc("knee:reverse-functions") val product = CodegenFunction(spec) - codegen.prepareContainer( + codegen.ensureContainer( declaration = implementation, importInfo = kind.importInfo, detectPropertyAccessors = false, // we don't generate properties at all in the companion object @@ -128,7 +131,17 @@ private fun KneeUpwardFunction.makeIr(context: KneeContext, signature: UpwardFun // Configure value parameters. First option is 'copyParameterDeclarationsFrom(source)' // but that copies type parameters too, fails for generics. We have concrete types. // Use the import susbstitution map instead, or TODO: use signature value parameters - copyValueParametersFrom(source, kind.importInfo?.substitutionMap ?: emptyMap()) + // copyValueParametersFrom(source, kind.importInfo?.substitutionMap ?: emptyMap()) + + parameters = source.parameters.map { sourceParam -> + val codec = signature.regularParameters.find { it.first == sourceParam.name }?.second + sourceParam.copyTo( + irFunction = this, + type = codec?.localIrType + ?: kind.importInfo?.substitutor?.substitute(sourceParam.type.simple("makeIr")) as? IrSimpleType + ?: sourceParam.type + ) + } // This function overrides the source function // Could also += source.overriddenSymbols, not sure if needed, we're not doing it elsewhere @@ -137,17 +150,20 @@ private fun KneeUpwardFunction.makeIr(context: KneeContext, signature: UpwardFun val logPrefix = "ReverseFunctions.kt(${source.fqNameWhenAvailable})" body = DeclarationIrBuilder(context.plugin, symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).irBlockBody { context.log.injectLog(this, "$logPrefix INVOKED, retrieving jvm info") + val virtualMachine = irTemporary(kind.parent.irGetVirtualMachine(this)) val jvmMethodOwner = irTemporary(kind.parent.irGetMethodOwner(this)) val jvmMethod = irTemporary(kind.parent.irGetMethod(this, signature)) val jvmObject = irTemporary(kind.parent.irGetJvmObject(this)) - val args = valueParameters + val args = parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } if (!signature.isSuspend) { +irReturn(irCall( callee = context.symbols.functions(useEnv).single() ).apply { - extensionReceiver = kind.parent.irGetVirtualMachine(this@irBlockBody) - putTypeArgument(0, signature.result.localIrType) - putValueArgument(0, irLambda( + val extensionIndex = symbol.owner.parameters.indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } + val blockIndex = symbol.owner.parameters.indexOfFirst { it.name.asString() == "block" } + arguments[extensionIndex] = irGet(virtualMachine) + typeArguments[0] = signature.result.localIrType + arguments[blockIndex] = irLambda( context = context, parent = parent, content = { lambda -> @@ -161,28 +177,41 @@ private fun KneeUpwardFunction.makeIr(context: KneeContext, signature: UpwardFun +irReturn(irReceive(raw, signature, codecContext)) } } - )) + ) }) } else { // See kneeInvokeKnSuspend signature in runtime context.log.injectLog(this, "$logPrefix suspend machinery started") +irReturn(irCall(context.symbols.functions(kneeInvokeKnSuspend).single()).apply { - putTypeArgument(0, signature.result.encodedType.knOrNull ?: context.symbols.builtIns.unitType) - putTypeArgument(1, signature.result.localIrType) - putValueArgument(0, kind.parent.irGetVirtualMachine(this@irBlockBody)) - putValueArgument(1, irLambda(context, parent) { lambda -> - val env = lambda.addValueParameter("_env", envType) + typeArguments[0] = signature.result.encodedType.knOrNull ?: context.symbols.builtIns.unitType + typeArguments[1] = signature.result.localIrType + arguments[0] = irGet(virtualMachine) + arguments[1] = irLambda(context, parent) { lambda -> val invoker = lambda.addValueParameter("_invoker", context.symbols.builtIns.longType) lambda.returnType = signature.suspendResult.localIrType - with(UpwardFunctionsIr) { - val codecContext = IrCodecContext(source.symbol, env, true, context.log) - context.log.injectLog(this@irBlockBody, "$logPrefix preparing the JVM call") - val raw = irInvoke(context.symbols, args, signature, codecContext, jvmObject, jvmMethodOwner, jvmMethod, signature.suspendResult.encodedType, invoker) - context.log.injectLog(this@irBlockBody, "$logPrefix received the invocation token") - +irReturn(irReceive(raw, signature, codecContext, suspendToken = true)) - } - }) - putValueArgument(2, irLambda(context, parent) { lambda -> + +irReturn(irCall(context.symbols.functions(useEnv).single()).apply { + val extensionIndex = symbol.owner.parameters.indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } + val blockIndex = symbol.owner.parameters.indexOfFirst { it.name.asString() == "block" } + arguments[extensionIndex] = irGet(virtualMachine) + typeArguments[0] = signature.suspendResult.localIrType + arguments[blockIndex] = irLambda(context, parent) { envLambda -> + envLambda.returnType = signature.suspendResult.localIrType + val env = envLambda.addValueParameter("_env", envType) + with(UpwardFunctionsIr) { + val codecContext = IrCodecContext(source.symbol, env, true, context.log) + context.log.injectLog(this@irBlockBody, "$logPrefix preparing the JVM call") + val raw = irInvoke(context.symbols, args, signature, codecContext, jvmObject, jvmMethodOwner, jvmMethod, signature.suspendResult.encodedType, invoker) + val invocation = irReceive(raw, signature, codecContext, suspendToken = true) + context.log.injectLog(this@irBlockBody, "$logPrefix received the invocation token") + +irReturn(irCall(context.symbols.functions(kneeGlobalize).single()).apply { + arguments[0] = irGet(env) + arguments[1] = invocation + }) + } + } + }) + } + arguments[2] = irLambda(context, parent) { lambda -> val env = lambda.addValueParameter("_env", envType) val raw = lambda.addValueParameter("_result", signature.result.encodedType.knOrNull ?: context.symbols.builtIns.unitType) lambda.returnType = signature.result.localIrType @@ -191,11 +220,11 @@ private fun KneeUpwardFunction.makeIr(context: KneeContext, signature: UpwardFun context.log.injectLog(this@irBlockBody, "$logPrefix received the suspend function result. unwrapping it") +irReturn(irReceive(irGet(raw), signature, codecContext)) } - }) + } }) } } }.also { irProducts.add(it) } -} \ No newline at end of file +} diff --git a/knee-compiler-plugin/src/main/kotlin/codec/BufferCodecs.kt b/knee-compiler-plugin/src/main/kotlin/codec/BufferCodecs.kt index 629bc07..d399d58 100644 --- a/knee-compiler-plugin/src/main/kotlin/codec/BufferCodecs.kt +++ b/knee-compiler-plugin/src/main/kotlin/codec/BufferCodecs.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.ir.builders.IrStatementsBuilder import org.jetbrains.kotlin.ir.builders.irCall import org.jetbrains.kotlin.ir.builders.irCallConstructor import org.jetbrains.kotlin.ir.builders.irGet +import org.jetbrains.kotlin.ir.declarations.IrParameterKind import org.jetbrains.kotlin.ir.declarations.IrValueDeclaration import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.types.IrSimpleType @@ -46,7 +47,7 @@ private class BufferCodec( private val objGetter = localIrType.classOrNull!!.getPropertyGetter("obj")!! private val createBuffer = localIrType.classOrNull!!.constructors.single { - val params = it.owner.valueParameters + val params = it.owner.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } params.size == 2 && params[1].type == symbols.typeAliasUnwrapped(PlatformIds.jobject) } @@ -59,8 +60,8 @@ private class BufferCodec( override fun IrStatementsBuilder<*>.irDecode(irContext: IrCodecContext, jni: IrValueDeclaration): IrExpression { return irCallConstructor(createBuffer, emptyList()).apply { - putValueArgument(0, irGet(irContext.environment)) - putValueArgument(1, irGet(jni)) + arguments[0] = irGet(irContext.environment) + arguments[1] = irGet(jni) } } diff --git a/knee-compiler-plugin/src/main/kotlin/codec/CollectionCodec.kt b/knee-compiler-plugin/src/main/kotlin/codec/CollectionCodec.kt index fcf195e..3adc7de 100644 --- a/knee-compiler-plugin/src/main/kotlin/codec/CollectionCodec.kt +++ b/knee-compiler-plugin/src/main/kotlin/codec/CollectionCodec.kt @@ -11,6 +11,7 @@ import io.deepmedia.tools.knee.plugin.compiler.symbols.RuntimeIds.typedArraySpec import io.deepmedia.tools.knee.plugin.compiler.utils.irLambda import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrParameterKind import org.jetbrains.kotlin.ir.declarations.IrValueDeclaration import org.jetbrains.kotlin.ir.expressions.IrDeclarationReference import org.jetbrains.kotlin.ir.expressions.IrExpression @@ -95,7 +96,7 @@ class CollectionCodec( is JniType.Void -> error("Void is not allowed here.") is JniType.Primitive -> irGetObject(runtimeHelperClassRaw.symbol) is JniType.Object -> irCallConstructor(runtimeHelperClassRaw.primaryConstructor!!.symbol, emptyList()).apply { - putValueArgument(0, irString(type.jvm.jvmClassName)) + arguments[0] = irString(type.jvm.jvmClassName) } } } @@ -112,11 +113,11 @@ class CollectionCodec( CollectionKind.Array.getCollectionTypeOf(elementCodec.localIrType, this@CollectionCodec.context.symbols) )).apply { // Constructor param: CollectionCodec - putValueArgument(0, rawHelper) - // Constructor param: ArraySpec - // Return type of this is symbols.klass(runtimeArraySpecClass) - // .typeWith(CollectionKind.Array.getCollectionType(elementCodec.localType, symbols), elementCodec.localType) - putValueArgument(1, when (val type = elementCodec.encodedType) { + arguments[0] = rawHelper +// Constructor param: ArraySpec +// Return type of this is symbols.klass(runtimeArraySpecClass) +// .typeWith(CollectionKind.Array.getCollectionType(elementCodec.localType, symbols), elementCodec.localType) + arguments[1] = when (val type = elementCodec.encodedType) { is JniType.Void -> error("Void is not allowed here.") is JniType.Primitive -> { val name = PrimitiveArraySpec(type.jvmSimpleName) @@ -125,30 +126,37 @@ class CollectionCodec( is JniType.Object -> { val name = typedArraySpec irCall(this@CollectionCodec.context.symbols.functions(name).single()).apply { - putTypeArgument(0, type.kn) + typeArguments[0] = type.kn } } - }) - // Constructor param: Source --> Transformed decoding lambda - putValueArgument(2, irLambda( + } +// Constructor param: Source --> Transformed decoding lambda + arguments[2] = irLambda( context = this@CollectionCodec.context, parent = this@irGetOrCreateHelper.parent, valueParameters = listOf(elementCodec.encodedType.knOrNull!!), returnType = elementCodec.localIrType, content = { lambda -> - +irReturn(with(elementCodec) { irDecode(codecContext, lambda.valueParameters[0]) }) + +irReturn(with(elementCodec) { irDecode(codecContext, lambda.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[0]) }) } - )) - // Constructor param: Transformed --> Source encoding lambda - putValueArgument(3, irLambda( + ) +// Constructor param: Transformed --> Source encoding lambda + arguments[3] = irLambda( context = this@CollectionCodec.context, parent = this@irGetOrCreateHelper.parent, valueParameters = listOf(elementCodec.localIrType), returnType = elementCodec.encodedType.knOrNull!!, content = { lambda -> - +irReturn(with(elementCodec) { irEncode(codecContext, lambda.valueParameters[0]) }) + +irReturn( + with(elementCodec) { + irEncode( + codecContext, + lambda.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[0] + ) + } + ) } - )) + ) } } @@ -159,8 +167,12 @@ class CollectionCodec( val decode = runtimeHelperClass.functions.single { it.name.asString() == "decodeInto${collectionKind.name}" } return irCall(decode).apply { dispatchReceiver = irGet(codec) - extensionReceiver = irGet(irContext.environment) - putValueArgument(0, irGet(jni)) + val extensionIndex = symbol.owner.parameters + .indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } + val arrayIndex = symbol.owner.parameters + .indexOfFirst { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + arguments[extensionIndex] = irGet(irContext.environment) + arguments[arrayIndex] = irGet(jni) } } @@ -170,8 +182,12 @@ class CollectionCodec( val encode = runtimeHelperClass.functions.single { it.name.asString() == "encode${collectionKind.name}" } return irCall(encode).apply { dispatchReceiver = irGet(codec) - extensionReceiver = irGet(irContext.environment) - putValueArgument(0, irGet(local)) + val extensionIndex = symbol.owner.parameters + .indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } + val collectionIndex = symbol.owner.parameters + .indexOfFirst { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + arguments[extensionIndex] = irGet(irContext.environment) + arguments[collectionIndex] = irGet(local) } } @@ -239,4 +255,4 @@ class CollectionCodec( } } } -} \ No newline at end of file +} diff --git a/knee-compiler-plugin/src/main/kotlin/codec/GenericCodec.kt b/knee-compiler-plugin/src/main/kotlin/codec/GenericCodec.kt index aa407e4..4cf48f4 100644 --- a/knee-compiler-plugin/src/main/kotlin/codec/GenericCodec.kt +++ b/knee-compiler-plugin/src/main/kotlin/codec/GenericCodec.kt @@ -44,8 +44,8 @@ class GenericCodec( } fun irEncodeBoxed(type: String) = irCall(symbols.functions(encodeBoxed(type)).single()).apply { - putValueArgument(0, irGet(irContext.environment)) - putValueArgument(1, data) + arguments[0] = irGet(irContext.environment) + arguments[1] = data } return when (wrappedType) { @@ -62,8 +62,8 @@ class GenericCodec( override fun IrStatementsBuilder<*>.irDecode(irContext: IrCodecContext, jni: IrValueDeclaration): IrExpression { fun irDecodeBoxed(type: String) = irCall(symbols.functions(decodeBoxed(type)).single()).apply { - putValueArgument(0, irGet(irContext.environment)) - putValueArgument(1, irGet(jni)) + arguments[0] = irGet(irContext.environment) + arguments[1] = irGet(jni) } val decoded = when (wrappedType) { diff --git a/knee-compiler-plugin/src/main/kotlin/codec/PrimitiveCodecs.kt b/knee-compiler-plugin/src/main/kotlin/codec/PrimitiveCodecs.kt index e6c9e8a..a509bd7 100644 --- a/knee-compiler-plugin/src/main/kotlin/codec/PrimitiveCodecs.kt +++ b/knee-compiler-plugin/src/main/kotlin/codec/PrimitiveCodecs.kt @@ -29,13 +29,13 @@ private class BooleanCodec(symbols: KneeSymbols) : Codec(symbols.builtIns.boolea override fun IrStatementsBuilder<*>.irDecode(irContext: IrCodecContext, jni: IrValueDeclaration): IrExpression { return irCall(decode).apply { - putValueArgument(0, irGet(jni)) + arguments[0] = irGet(jni) } } override fun IrStatementsBuilder<*>.irEncode(irContext: IrCodecContext, local: IrValueDeclaration): IrExpression { return irCall(create).apply { - putValueArgument(0, irGet(local)) + arguments[0] = irGet(local) } } diff --git a/knee-compiler-plugin/src/main/kotlin/codec/StringCodecs.kt b/knee-compiler-plugin/src/main/kotlin/codec/StringCodecs.kt index 005bd86..e9f56f0 100644 --- a/knee-compiler-plugin/src/main/kotlin/codec/StringCodecs.kt +++ b/knee-compiler-plugin/src/main/kotlin/codec/StringCodecs.kt @@ -26,15 +26,15 @@ private class StringCodec(symbols: KneeSymbols) : Codec( override fun IrStatementsBuilder<*>.irDecode(irContext: IrCodecContext, jni: IrValueDeclaration): IrExpression { return irCall(decode).apply { - putValueArgument(0, irGet(irContext.environment)) - putValueArgument(1, irGet(jni)) + arguments[0] = irGet(irContext.environment) + arguments[1] = irGet(jni) } } override fun IrStatementsBuilder<*>.irEncode(irContext: IrCodecContext, local: IrValueDeclaration): IrExpression { return irCall(encode).apply { - putValueArgument(0, irGet(irContext.environment)) - putValueArgument(1, irGet(local)) + arguments[0] = irGet(irContext.environment) + arguments[1] = irGet(local) } } diff --git a/knee-compiler-plugin/src/main/kotlin/codec/UnsignedCodecs.kt b/knee-compiler-plugin/src/main/kotlin/codec/UnsignedCodecs.kt index 1914b01..11d4011 100644 --- a/knee-compiler-plugin/src/main/kotlin/codec/UnsignedCodecs.kt +++ b/knee-compiler-plugin/src/main/kotlin/codec/UnsignedCodecs.kt @@ -8,6 +8,7 @@ import org.jetbrains.kotlin.backend.jvm.functionByName import org.jetbrains.kotlin.ir.builders.IrStatementsBuilder import org.jetbrains.kotlin.ir.builders.irCall import org.jetbrains.kotlin.ir.builders.irGet +import org.jetbrains.kotlin.ir.declarations.IrParameterKind import org.jetbrains.kotlin.ir.declarations.IrValueDeclaration import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.types.typeWith @@ -30,12 +31,12 @@ private class UnsignedCodec( override fun toString(): String = description private val toUnsigned = symbols.functions(toUnsignedFunctions).single { - it.owner.extensionReceiverParameter?.type == signed.kn + it.owner.parameters.firstOrNull { it.kind == IrParameterKind.ExtensionReceiver }?.type == signed.kn } private val toSigned = symbols.klass(unsignedClass).functionByName(toSignedFunction) override fun IrStatementsBuilder<*>.irDecode(irContext: IrCodecContext, jni: IrValueDeclaration): IrExpression { - return irCall(toUnsigned).apply { extensionReceiver = irGet(jni) } + return irCall(toUnsigned).apply { arguments[symbol.owner.parameters.indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver }] = irGet(jni) } } override fun IrStatementsBuilder<*>.irEncode(irContext: IrCodecContext, local: IrValueDeclaration): IrExpression { diff --git a/knee-compiler-plugin/src/main/kotlin/codegen/CodegenDeclaration.kt b/knee-compiler-plugin/src/main/kotlin/codegen/CodegenDeclaration.kt index cfa7397..7393ef5 100644 --- a/knee-compiler-plugin/src/main/kotlin/codegen/CodegenDeclaration.kt +++ b/knee-compiler-plugin/src/main/kotlin/codegen/CodegenDeclaration.kt @@ -60,9 +60,11 @@ class CodegenFile(spec: FileSpec.Builder) : CodegenDeclaration class CodegenFunction(spec: FunSpec.Builder, val isPrimaryConstructor: Boolean = false) : CodegenDeclaration(spec) { override val uid by lazy { - "Fun(${spec.build().name}, ${spec.parameters.joinToString { parameterSpec -> + val functionName = spec.build().name + val parameters = spec.parameters.joinToString { parameterSpec -> parameterSpec.type.disambiguationName - }})" + } + "Fun($functionName, $parameters)" } override val modifiers get() = spec.modifiers diff --git a/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt b/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt index 6d87994..c391874 100644 --- a/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt +++ b/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt @@ -9,7 +9,6 @@ import io.deepmedia.tools.knee.plugin.compiler.import.writableParent import io.deepmedia.tools.knee.plugin.compiler.utils.asPropertySpec import io.deepmedia.tools.knee.plugin.compiler.utils.asTypeSpec import io.deepmedia.tools.knee.plugin.compiler.utils.canonicalName -import org.jetbrains.kotlin.backend.jvm.ir.propertyIfAccessor import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.FqName @@ -19,6 +18,7 @@ class KneeCodegen(private val context: KneeContext, val root: File, val verbose: companion object { const val Filename = "Knee" } + init { root.deleteRecursively() root.mkdirs() @@ -39,22 +39,80 @@ class KneeCodegen(private val context: KneeContext, val root: File, val verbose: } } - fun prepareContainer( + fun ensureContainer( + declaration: IrDeclaration, + importInfo: ImportInfo?, + detectPropertyAccessors: Boolean = true, + createCompanionObject: Boolean = false, + ) = runCatching { + containerForDeclaration( + declaration, + importInfo, + detectPropertyAccessors, + createCompanionObject + ) + }.recover { + prepareContainer( + declaration, + importInfo, + detectPropertyAccessors, + createCompanionObject + ) + }.getOrThrow() + + fun containerForDeclaration( declaration: IrDeclaration, importInfo: ImportInfo?, detectPropertyAccessors: Boolean = true, createCompanionObject: Boolean = false, ): CodegenDeclaration<*> { - val irHierarchy: MutableList = when (val container = declaration.writableParent(context, importInfo)) { - is IrDeclaration -> container.parentsWithSelf.toMutableList() - else -> mutableListOf(container) + val classHierarchy = mutableListOf() + var current: IrDeclarationParent? = declaration.parent + while (current != null && current !is IrFile) { + if (current is IrClass) { + classHierarchy.add(current) + } + current = (current as? IrDeclaration)?.parent } + val irFile = current ?: error("Declaration not inside a file: $declaration") + + var container: CodegenDeclaration<*> = file(irFile.packageFqName.asString()) + for (irClass in classHierarchy.reversed()) { + container = container.addChildIfNeeded(CodegenClass(irClass.asTypeSpec())) + } + + if (createCompanionObject && container is CodegenClass && !container.isCompanion) { + container = container.addChildIfNeeded(CodegenClass(TypeSpec.companionObjectBuilder())) + } + if (detectPropertyAccessors && (declaration.isSetter || declaration.isGetter)) { + val irProperty = (declaration as IrFunction).propertyIfAccessor as IrProperty + container = container.addChildIfNeeded(CodegenProperty(irProperty.asPropertySpec())) + } + return container + } + + fun prepareContainer( + declaration: IrDeclaration, + importInfo: ImportInfo?, + detectPropertyAccessors: Boolean = true, + createCompanionObject: Boolean = false, + ): CodegenDeclaration<*> { + val irHierarchy: MutableList = + when (val container = declaration.writableParent(context, importInfo)) { + is IrDeclaration -> container.parentsWithSelf.toMutableList() + else -> mutableListOf(container) + } // irHierarchy is a list which goes from the parent of declaration up until the file // [parentOfDeclaration, ... , ... , declarationFile] // We will then go from last to first and add all needed CodegenDeclarations - var candidate: CodegenDeclaration<*> = file((irHierarchy.removeLast() as IrFile).packageFqName.asString()) - + val packageFqName = when (val last = irHierarchy.removeLast()) { + is IrClass -> last.packageFqName + is IrFile -> last.packageFqName + else -> null + } ?: error("Declaration parent is not an IrClass or IrFile: $declaration") + var candidate: CodegenDeclaration<*> = file(packageFqName.asString()) + while (irHierarchy.isNotEmpty()) { val irParent = irHierarchy.removeLast() require(irParent is IrClass) { "Declaration parent is not an IrClass: $irParent (import=$importInfo all=${irHierarchy})" } @@ -74,7 +132,7 @@ class KneeCodegen(private val context: KneeContext, val root: File, val verbose: } return candidate } - + /* fun containerOf( declaration: IrDeclaration, importInfo: ImportInfo?, @@ -120,7 +178,7 @@ class KneeCodegen(private val context: KneeContext, val root: File, val verbose: return !modifiers.contains(KModifier.PRIVATE) && !modifiers.contains(KModifier.INTERNAL) } - private fun CodegenDeclaration.prepare(): T { + private fun CodegenDeclaration.prepare(): T { val sorted = children.sortedByDescending { it.isProbablyPublic() } sorted.forEach { when (it) { @@ -128,19 +186,29 @@ class KneeCodegen(private val context: KneeContext, val root: File, val verbose: is CodegenFunction -> { val funSpec = it.prepare().build() when (this) { - is CodegenFile -> spec.addFunction(funSpec) + is CodegenFile -> { + if (funSpec.isConstructor) { + // spec.members += funSpec + } else { + spec.addFunction(funSpec) + } + } + is CodegenClass -> when { it.isPrimaryConstructor -> spec.primaryConstructor(funSpec) else -> spec.addFunction(funSpec) // works for regular constructors too } + is CodegenProperty -> when { it.isGetter -> spec.getter(funSpec) it.isSetter -> spec.setter(funSpec) else -> error("Can't add CodegenFunction to CodegenProperty, name = ${funSpec.name}") } + is CodegenFunction -> error("Can't add CodegenFunction to CodegenFunction") } } + is CodegenClass -> { val typeSpec = it.prepare().build() when (this) { @@ -150,6 +218,7 @@ class KneeCodegen(private val context: KneeContext, val root: File, val verbose: is CodegenFunction -> error("Can't add CodegenType to CodegenFunction") } } + is CodegenProperty -> { val propertySpec = it.prepare().build() when (this) { diff --git a/knee-compiler-plugin/src/main/kotlin/context/KneeContext.kt b/knee-compiler-plugin/src/main/kotlin/context/KneeContext.kt index b96b68f..9488969 100644 --- a/knee-compiler-plugin/src/main/kotlin/context/KneeContext.kt +++ b/knee-compiler-plugin/src/main/kotlin/context/KneeContext.kt @@ -41,6 +41,6 @@ class KneeContext( val mapper by lazy { KneeMapper(this, json) } - val log = KneeLogger(log, verboseLogs, verboseRuntime) + val log = KneeLogger(this, log, verboseLogs, verboseRuntime) } diff --git a/knee-compiler-plugin/src/main/kotlin/context/KneeLogger.kt b/knee-compiler-plugin/src/main/kotlin/context/KneeLogger.kt index ceed78b..f6927e1 100644 --- a/knee-compiler-plugin/src/main/kotlin/context/KneeLogger.kt +++ b/knee-compiler-plugin/src/main/kotlin/context/KneeLogger.kt @@ -4,6 +4,7 @@ import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.MemberName import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.ir.InternalSymbolFinderAPI import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.IrDeclaration import org.jetbrains.kotlin.ir.declarations.IrValueDeclaration @@ -12,8 +13,13 @@ import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.types.makeNullable import org.jetbrains.kotlin.ir.util.file import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.ir.IrBuiltIns +import org.jetbrains.kotlin.ir.declarations.IrParameterKind +import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.FqName class KneeLogger( + private val kneeContext: KneeContext, private val collector: MessageCollector, private val verboseLogs: Boolean, private val verboseRuntime: Boolean @@ -35,15 +41,27 @@ class KneeLogger( fun injectLog(scope: IrStatementsBuilder<*>, message: String) { if (!verboseRuntime) return + @OptIn(InternalSymbolFinderAPI::class) if (printlnIrString == null) { - val builtIns = (scope.parent as IrDeclaration).file.module.irBuiltins - val function = builtIns.findFunctions(Name.identifier("println"), "kotlin", "io") - printlnIrString = function.single { it.owner.valueParameters.firstOrNull()?.type == builtIns.stringType } + val builtIns = kneeContext.plugin.irBuiltIns + val symbolFinder = builtIns.symbolFinder + + val function = symbolFinder.findFunctions( + CallableId( + FqName.fromSegments(listOf("kotlin", "io")), + Name.identifier("println") + ) + ) + printlnIrString = function.single { + it.owner.parameters + .firstOrNull { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + ?.type == builtIns.stringType + } } with(scope) { +irCall(printlnIrString!!).apply { - putValueArgument(0, scope.irString("[KNEE_KN] $message")) + arguments[0] = scope.irString("[KNEE_KN] $message") } } } @@ -51,15 +69,27 @@ class KneeLogger( fun injectLog(scope: IrStatementsBuilder<*>, objToPrint: IrValueDeclaration) { if (!verboseRuntime) return + @OptIn(InternalSymbolFinderAPI::class) if (printlnIrAny == null) { - val builtIns = (scope.parent as IrDeclaration).file.module.irBuiltins - val function = builtIns.findFunctions(Name.identifier("println"), "kotlin", "io") - printlnIrAny = function.single { it.owner.valueParameters.firstOrNull()?.type == builtIns.anyType.makeNullable() } + val builtIns = kneeContext.plugin.irBuiltIns + val symbolFinder = builtIns.symbolFinder + + val function = symbolFinder.findFunctions( + CallableId( + FqName.fromSegments(listOf("kotlin", "io")), + Name.identifier("println") + ) + ) + printlnIrAny = function.single { + it.owner.parameters + .firstOrNull { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + ?.type == builtIns.anyType.makeNullable() + } } with(scope) { +irCall(printlnIrAny!!).apply { - putValueArgument(0, irGet(objToPrint)) + arguments[0] = irGet(objToPrint) } } } diff --git a/knee-compiler-plugin/src/main/kotlin/context/KneeMapper.kt b/knee-compiler-plugin/src/main/kotlin/context/KneeMapper.kt index 7c5026f..54116e9 100644 --- a/knee-compiler-plugin/src/main/kotlin/context/KneeMapper.kt +++ b/knee-compiler-plugin/src/main/kotlin/context/KneeMapper.kt @@ -13,7 +13,6 @@ import io.deepmedia.tools.knee.plugin.compiler.utils.asTypeName import io.deepmedia.tools.knee.plugin.compiler.utils.isPartOf import io.deepmedia.tools.knee.plugin.compiler.utils.simple import kotlinx.serialization.json.Json -import org.jetbrains.kotlin.ir.backend.js.utils.valueArguments import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.expressions.IrConst @@ -21,6 +20,7 @@ import org.jetbrains.kotlin.ir.expressions.IrConstructorCall import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.util.isNullable class KneeMapper( private val context: KneeContext, @@ -48,9 +48,9 @@ class KneeMapper( // This representation is the best one because it shows type parameters private val IrType.description get() = runCatching { this.simple("IrType.description").asTypeName() }.getOrElse { this }.toString() - private val IrConstructorCall.description get() = "${type.description} ${valueArguments.map { it?.description }}" + private val IrConstructorCall.description get() = "${type.description} ${arguments.map { it?.description }}" - private val IrExpression.description get() = if (this is IrConst<*>) this.value.toString() else this.toString() + private val IrExpression.description get() = if (this is IrConst) this.value.toString() else this.toString() private fun errorDescription(type: IrType): String { val klass = type.classOrNull?.owner @@ -77,7 +77,7 @@ ${type.classOrNull?.owner?.annotations?.joinToString("\n") { "\t" + it.descripti fun get(type: IrType, useSiteAnnotations: IrAnnotationContainer? = null): Codec { val raw = useSiteAnnotations?.getAnnotation(AnnotationIds.KneeRaw) if (raw != null) { - val fqn = (raw.getValueArgument(0)!! as IrConst).value + val fqn = (raw.arguments[0]!! as IrConst).value as String val jobject = JniType.Object(context.symbols, CodegenType.from(fqn)) require(jobject.kn.makeNullable() == type.makeNullable()) { "@KneeRaw(${fqn}) should be applied on a parameter of type 'jobject' or similar CPointer type alias." diff --git a/knee-compiler-plugin/src/main/kotlin/export/v1/ExportAdapters.kt b/knee-compiler-plugin/src/main/kotlin/export/v1/ExportAdapters.kt index fd78b1e..243720c 100644 --- a/knee-compiler-plugin/src/main/kotlin/export/v1/ExportAdapters.kt +++ b/knee-compiler-plugin/src/main/kotlin/export/v1/ExportAdapters.kt @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.ir.builders.irBlockBody import org.jetbrains.kotlin.ir.builders.irReturn import org.jetbrains.kotlin.ir.builders.irReturnUnit import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrParameterKind import org.jetbrains.kotlin.ir.util.defaultType import org.jetbrains.kotlin.ir.util.functions import org.jetbrains.kotlin.ir.util.hasAnnotation @@ -71,9 +72,9 @@ object ExportAdapters { // Note: reverse = false but we don't relly know if the obj being converted is a param or return type // TODO: reconsider this reverse flag as it does not generalize properly to export specs val codecContext = - IrCodecContext(null, read.valueParameters[0], false, context.log) + IrCodecContext(null, read.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[0], false, context.log) with(codec) { - +irReturn(irDecode(codecContext, read.valueParameters[1])) + +irReturn(irDecode(codecContext, read.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[1])) } } @@ -81,9 +82,9 @@ object ExportAdapters { // Note: reverse = false but we don't relly know if the obj being converted is a param or return type // We should reconsider this reverse flag as it does not generalize properly to export specs val codecContext = - IrCodecContext(null, write.valueParameters[0], false, context.log) + IrCodecContext(null, write.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[0], false, context.log) with(codec) { - +irReturn(irEncode(codecContext, write.valueParameters[1])) + +irReturn(irEncode(codecContext, write.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[1])) } } } diff --git a/knee-compiler-plugin/src/main/kotlin/export/v1/ExportFlags.kt b/knee-compiler-plugin/src/main/kotlin/export/v1/ExportFlags.kt index 35ed75c..a7926fa 100644 --- a/knee-compiler-plugin/src/main/kotlin/export/v1/ExportFlags.kt +++ b/knee-compiler-plugin/src/main/kotlin/export/v1/ExportFlags.kt @@ -17,7 +17,7 @@ val IrClass.hasExport1Flag: Boolean get() { val a = e.getValueArgument(Name.identifier("exported")) ?: return false @Suppress("UNCHECKED_CAST") - return (a as? IrConst)?.value ?: false + return (a as? IrConst)?.value as? Boolean ?: false } val ClassDescriptor.hasExport1Flag: Boolean get() { diff --git a/knee-compiler-plugin/src/main/kotlin/export/v1/ExportInfo.kt b/knee-compiler-plugin/src/main/kotlin/export/v1/ExportInfo.kt index ffd178e..167dfd5 100644 --- a/knee-compiler-plugin/src/main/kotlin/export/v1/ExportInfo.kt +++ b/knee-compiler-plugin/src/main/kotlin/export/v1/ExportInfo.kt @@ -10,7 +10,6 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import kotlinx.serialization.json.Json -import org.jetbrains.kotlin.ir.backend.js.utils.valueArguments import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.expressions.IrConst import org.jetbrains.kotlin.ir.util.functions @@ -28,9 +27,10 @@ val IrClass.exportInfo: ExportInfo? get() { .first { it.name == ExportInfo.DeclarationNames.AnnotatedFunction } .annotations .single() - .valueArguments[0] - .let { it as IrConst } + .arguments[0] + .let { it as IrConst } .value + .let { it as String } .let { Json.decodeFromString(it) } } diff --git a/knee-compiler-plugin/src/main/kotlin/export/v1/ExportedCodec1.kt b/knee-compiler-plugin/src/main/kotlin/export/v1/ExportedCodec1.kt index 352efc8..3f71778 100644 --- a/knee-compiler-plugin/src/main/kotlin/export/v1/ExportedCodec1.kt +++ b/knee-compiler-plugin/src/main/kotlin/export/v1/ExportedCodec1.kt @@ -69,8 +69,8 @@ class ExportedCodec1(symbols: KneeSymbols, type: IrType, private val exportInfo: ): IrExpression { return irCall(irSpec.functions.first { it.name.asString() == "read" }).apply { dispatchReceiver = irGetObject(irSpec.symbol) - putValueArgument(0, irGet(irContext.environment)) - putValueArgument(1, irGet(jni)) + arguments[0] = irGet(irContext.environment) + arguments[1] = irGet(jni) } } @@ -80,8 +80,8 @@ class ExportedCodec1(symbols: KneeSymbols, type: IrType, private val exportInfo: ): IrExpression { return irCall(irSpec.functions.first { it.name.asString() == "write" }).apply { dispatchReceiver = irGetObject(irSpec.symbol) - putValueArgument(0, irGet(irContext.environment)) - putValueArgument(1, irGet(local)) + arguments[0] = irGet(irContext.environment) + arguments[1] = irGet(local) } } diff --git a/knee-compiler-plugin/src/main/kotlin/export/v2/ExportAdapters2.kt b/knee-compiler-plugin/src/main/kotlin/export/v2/ExportAdapters2.kt index 31a55b5..4b3d1d8 100644 --- a/knee-compiler-plugin/src/main/kotlin/export/v2/ExportAdapters2.kt +++ b/knee-compiler-plugin/src/main/kotlin/export/v2/ExportAdapters2.kt @@ -11,6 +11,7 @@ import io.deepmedia.tools.knee.plugin.compiler.symbols.PlatformIds import io.deepmedia.tools.knee.plugin.compiler.utils.irLambda import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.IrParameterKind import org.jetbrains.kotlin.ir.expressions.IrConstructorCall import org.jetbrains.kotlin.ir.types.typeWith import org.jetbrains.kotlin.ir.util.constructors @@ -47,7 +48,7 @@ object ExportAdapters2 { val jniEnvironmentType = context.symbols.klass(CInteropIds.CPointer).typeWith(context.symbols.typeAliasUnwrapped(PlatformIds.JNIEnvVar)) return irCallConstructor(adapterClass.constructors.single(), listOf(info.encodedType.kn, info.localIrType)).apply { // Encode - putValueArgument(0, irLambda( + arguments[0] = irLambda( context = context, parent = parent, valueParameters = listOf(jniEnvironmentType, info.localIrType), @@ -55,13 +56,13 @@ object ExportAdapters2 { content = { lambda -> // Note: reverse = false but we don't relly know if the obj being converted is a param or return type // TODO: reconsider this reverse flag as it does not generalize properly to export specs - val codecContext = IrCodecContext(null, lambda.valueParameters[0], false, context.log) + val codecContext = IrCodecContext(null, lambda.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[0], false, context.log) val codec = context.mapper.get(info.localIrType) - with(codec) { +irReturn(irEncode(codecContext, lambda.valueParameters[1])) } + with(codec) { +irReturn(irEncode(codecContext, lambda.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[1])) } } - )) + ) // Decode - putValueArgument(1, irLambda( + arguments[1] = irLambda( context = context, parent = parent, valueParameters = listOf(jniEnvironmentType, info.encodedType.kn), @@ -69,11 +70,11 @@ object ExportAdapters2 { content = { lambda -> // Note: reverse = false but we don't relly know if the obj being converted is a param or return type // TODO: reconsider this reverse flag as it does not generalize properly to export specs - val codecContext = IrCodecContext(null, lambda.valueParameters[0], false, context.log) + val codecContext = IrCodecContext(null, lambda.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[0], false, context.log) val codec = context.mapper.get(info.localIrType) - with(codec) { +irReturn(irDecode(codecContext, lambda.valueParameters[1])) } + with(codec) { +irReturn(irDecode(codecContext, lambda.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }[1])) } } - )) + ) } } } \ No newline at end of file diff --git a/knee-compiler-plugin/src/main/kotlin/export/v2/ExportedCodec2.kt b/knee-compiler-plugin/src/main/kotlin/export/v2/ExportedCodec2.kt index 607df47..e251b2b 100644 --- a/knee-compiler-plugin/src/main/kotlin/export/v2/ExportedCodec2.kt +++ b/knee-compiler-plugin/src/main/kotlin/export/v2/ExportedCodec2.kt @@ -28,9 +28,9 @@ class ExportedCodec2(symbols: KneeSymbols, exportingModule: IrClass, exportedTyp private fun IrStatementsBuilder<*>.irGetAdapter(): IrExpression { return irCall(getAdapterFunction).apply { dispatchReceiver = irGetObject(moduleObject.symbol) - putTypeArgument(0, encodedType.knOrNull!!) - putTypeArgument(1, localIrType) - putValueArgument(0, irInt(typeId)) + typeArguments[0] = encodedType.knOrNull!! + typeArguments[1] = localIrType + arguments[0] = irInt(typeId) } } @@ -40,8 +40,8 @@ class ExportedCodec2(symbols: KneeSymbols, exportingModule: IrClass, exportedTyp ): IrExpression { return irCall(adapterDecodeFunction).apply { dispatchReceiver = irGetAdapter() - putValueArgument(0, irGet(irContext.environment)) - putValueArgument(1, irGet(jni)) + arguments[0] = irGet(irContext.environment) + arguments[1] = irGet(jni) } } @@ -51,8 +51,8 @@ class ExportedCodec2(symbols: KneeSymbols, exportingModule: IrClass, exportedTyp ): IrExpression { return irCall(adapterEncodeFunction).apply { dispatchReceiver = irGetAdapter() - putValueArgument(0, irGet(irContext.environment)) - putValueArgument(1, irGet(local)) + arguments[0] = irGet(irContext.environment) + arguments[1] = irGet(local) } } diff --git a/knee-compiler-plugin/src/main/kotlin/features/KneeCollector.kt b/knee-compiler-plugin/src/main/kotlin/features/KneeCollector.kt index 7498b5d..0dc9c23 100644 --- a/knee-compiler-plugin/src/main/kotlin/features/KneeCollector.kt +++ b/knee-compiler-plugin/src/main/kotlin/features/KneeCollector.kt @@ -13,7 +13,7 @@ import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.isPropertyAccessor import org.jetbrains.kotlin.ir.visitors.* -class KneeCollector(module: IrModuleFragment) : IrElementVisitorVoid { +class KneeCollector(module: IrModuleFragment) : IrVisitorVoid() { val initializers = mutableListOf() diff --git a/knee-compiler-plugin/src/main/kotlin/features/KneeDownwardFunction.kt b/knee-compiler-plugin/src/main/kotlin/features/KneeDownwardFunction.kt index dca3958..e8339ea 100644 --- a/knee-compiler-plugin/src/main/kotlin/features/KneeDownwardFunction.kt +++ b/knee-compiler-plugin/src/main/kotlin/features/KneeDownwardFunction.kt @@ -6,6 +6,7 @@ import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrConstructor import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.util.* class KneeDownwardFunction( @@ -59,4 +60,8 @@ class KneeDownwardFunction( init { source.requireNotComplex(this, allowSuspend = true) } + + // Imported interface members are invoked through a local trampoline to avoid + // calling external suspend interface symbols directly from generated bridge IR. + var importedAdapter: IrSimpleFunction? = null } diff --git a/knee-compiler-plugin/src/main/kotlin/features/KneeEnum.kt b/knee-compiler-plugin/src/main/kotlin/features/KneeEnum.kt index a548894..0ec27bb 100644 --- a/knee-compiler-plugin/src/main/kotlin/features/KneeEnum.kt +++ b/knee-compiler-plugin/src/main/kotlin/features/KneeEnum.kt @@ -6,7 +6,7 @@ import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrEnumEntry -import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.IrVisitorVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid class KneeEnum( @@ -19,7 +19,7 @@ class KneeEnum( init { source.requireNotComplex(this, ClassKind.ENUM_CLASS) val entries = mutableListOf() - source.acceptChildrenVoid(object : IrElementVisitorVoid { + source.acceptChildrenVoid(object : IrVisitorVoid() { override fun visitElement(element: IrElement) = Unit override fun visitEnumEntry(declaration: IrEnumEntry) { entries.add(declaration) diff --git a/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionSignature.kt b/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionSignature.kt index 1648496..b5be200 100644 --- a/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionSignature.kt +++ b/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionSignature.kt @@ -20,6 +20,7 @@ import io.deepmedia.tools.knee.plugin.compiler.symbols.RuntimeIds.KneeSuspendInv import org.jetbrains.kotlin.ir.declarations.IrConstructor import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.declarations.IrParameterKind import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.Name @@ -113,7 +114,7 @@ class DownwardFunctionSignature(source: IrFunction, kind: Kind, context: KneeCon } } - val regularParameters: List> = source.valueParameters.map { + val regularParameters: List> = source.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }.map { it.name to context.mapper.get(it.type.simple("DownwardSignature.regularParams").concrete(kind.importInfo), it) /* it.name to when (val rawKind = it.rawKind) { null -> mapper.get(it.type, kind.importInfo) @@ -151,7 +152,7 @@ class DownwardFunctionSignature(source: IrFunction, kind: Kind, context: KneeCon * One could just use type.asTypeName() but we must pass through the mapper for some edge scenarios, * like @KneeRaw-annotated declarations or other things. */ - val unsubstitutedValueParametersForCodegen: List> = source.valueParameters.map { + val unsubstitutedValueParametersForCodegen: List> = source.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }.map { it.name to (runCatching { context.mapper.get(it.type, it) }.getOrNull()?.localCodegenType?.name ?: it.type.simple("DownwardSignature.valueParams").asTypeName()) } @@ -227,7 +228,7 @@ class DownwardFunctionSignature(source: IrFunction, kind: Kind, context: KneeCon fun name(includeAncestors: Boolean): Name { // when ancestors are required for higher disambiguation, we must include the importInfo id. - val suffix = source.valueParameters.makeFunctionNameDisambiguationSuffix() + val suffix = source.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }.makeFunctionNameDisambiguationSuffix() val prefix = kind.importInfo?.id?.takeIf { includeAncestors } fun mapper(name: String): String = "$" + when (kind) { diff --git a/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionsIr.kt b/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionsIr.kt index b48bf09..47e9d05 100644 --- a/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionsIr.kt +++ b/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionsIr.kt @@ -4,6 +4,7 @@ import io.deepmedia.tools.knee.plugin.compiler.codec.IrCodecContext import io.deepmedia.tools.knee.plugin.compiler.functions.DownwardFunctionsIr.irInvoke import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.declarations.IrParameterKind import org.jetbrains.kotlin.ir.declarations.IrValueParameter import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable @@ -20,26 +21,59 @@ object DownwardFunctionsIr { fun IrStatementsBuilder<*>.irInvoke( inputs: List, local: IrFunction, + target: IrFunction, signature: DownwardFunctionSignature, codecContext: IrCodecContext, ): IrExpression { val logPrefix = "FunctionsIr.irInvoke(${local.fqNameWhenAvailable})" codecContext.logger.injectLog(this, "$logPrefix START") + val targetRegularParameterSlots = target.parameters.withIndex() + .filter { it.value.kind == IrParameterKind.Regular || it.value.kind == IrParameterKind.Context } + val targetHasDispatchReceiver = target.parameters.any { it.kind == IrParameterKind.DispatchReceiver } + val targetRegularBaseIndex = when { + targetHasDispatchReceiver -> 0 + signature.extraParameters.any { it.first == DownwardFunctionSignature.Extra.ReceiverInstance } -> 1 + else -> 0 + } - return irCall(local).apply { + fun targetArgumentIndex(name: org.jetbrains.kotlin.name.Name, fallbackRegularIndex: Int): Int { + return target.parameters + .indexOfFirst { parameter -> + (parameter.kind == IrParameterKind.Regular || parameter.kind == IrParameterKind.Context) && + parameter.name == name + } + .takeIf { it >= 0 } + ?: targetRegularParameterSlots + .getOrNull(fallbackRegularIndex) + ?.index + ?: error( + "Could not map parameter '$name' from ${local.fqNameWhenAvailable} to ${target.fqNameWhenAvailable}." + ) + } + + return irCall(target).apply { val hasReceiver = signature.extraParameters.firstOrNull { it.first == DownwardFunctionSignature.Extra.ReceiverInstance } hasReceiver?.let { (name, codec) -> val param = inputs.first { it.name == name } codecContext.logger.injectLog(this@irInvoke, "$logPrefix Decoding dispatch receiver $name with $codec") - dispatchReceiver = with(codec) { irDecode(codecContext, param) } + val decoded = with(codec) { irDecode(codecContext, param) } + when { + targetHasDispatchReceiver -> { + dispatchReceiver = decoded + } + else -> { + val targetIndex = targetArgumentIndex(name, 0) + arguments[targetIndex] = decoded + } + } } signature.regularParameters.forEachIndexed { index, (param, codec) -> with(codec) { // note: targetIndex != index because of copy parameters! val inputIndex = index + signature.knPrefixParameters.size + signature.extraParameters.size - val targetIndex = local.valueParameters.indexOfFirst { it.name == param } + val targetIndex = targetArgumentIndex(param, targetRegularBaseIndex + index) codecContext.logger.injectLog(this@irInvoke, "$logPrefix Decoding parameter $param with $codec") - putValueArgument(targetIndex, irDecode(codecContext, inputs[inputIndex])) + arguments[targetIndex] = irDecode(codecContext, inputs[inputIndex]) } } /* signature.knCopyParameters.forEach { (param, indexToBeCopied) -> @@ -65,4 +99,4 @@ object DownwardFunctionsIr { irEncode(codecContext, irTemporary(rawValue, "result")) } } -} \ No newline at end of file +} diff --git a/knee-compiler-plugin/src/main/kotlin/functions/ImportedInterfaceAdapters.kt b/knee-compiler-plugin/src/main/kotlin/functions/ImportedInterfaceAdapters.kt new file mode 100644 index 0000000..5e50b13 --- /dev/null +++ b/knee-compiler-plugin/src/main/kotlin/functions/ImportedInterfaceAdapters.kt @@ -0,0 +1,137 @@ +package io.deepmedia.tools.knee.plugin.compiler.functions + +import io.deepmedia.tools.knee.plugin.compiler.context.KneeContext +import io.deepmedia.tools.knee.plugin.compiler.context.KneeOrigin +import io.deepmedia.tools.knee.plugin.compiler.features.KneeDownwardFunction +import io.deepmedia.tools.knee.plugin.compiler.import.concrete +import io.deepmedia.tools.knee.plugin.compiler.symbols.RuntimeIds +import io.deepmedia.tools.knee.plugin.compiler.utils.asStringSafeForCodegen +import io.deepmedia.tools.knee.plugin.compiler.utils.simple +import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter +import org.jetbrains.kotlin.ir.builders.declarations.buildFun +import org.jetbrains.kotlin.ir.declarations.IrParameterKind +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.types.typeOrNull +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.name.Name + +internal fun KneeDownwardFunction.ensureImportedAdapter( + context: KneeContext, + signature: DownwardFunctionSignature +): IrSimpleFunction? { + val interfaceKind = kind as? KneeDownwardFunction.Kind.InterfaceMember ?: return null + val importInfo = interfaceKind.importInfo ?: return null + if (importedAdapter != null) return importedAdapter + + val file = importInfo.file + val adapterName = Name.identifier( + "kneeImported_" + signature.jniInfo.name(includeAncestors = true).asStringSafeForCodegen(true) + ) + val existing = file.findDeclaration { it.name == adapterName } + if (existing != null) { + importedAdapter = existing + return existing + } + + val source = source as IrSimpleFunction + val target = source.normalizedImportedTarget() + val helper = source.importedRuntimeHelper(context) + val receiverType = source.parentAsClass.thisReceiver!!.type.simple("ImportedInterfaceAdapter.receiver") + .concrete(importInfo) + val regularParams = source.parameters + .filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + val targetRegularParams = target.parameters + .filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + val targetRegularParamSlots = target.parameters.withIndex() + .filter { it.value.kind == IrParameterKind.Regular || it.value.kind == IrParameterKind.Context } + + return context.factory.buildFun { + startOffset = SYNTHETIC_OFFSET + endOffset = SYNTHETIC_OFFSET + origin = KneeOrigin.KNEE + visibility = DescriptorVisibilities.INTERNAL + modality = Modality.FINAL + name = adapterName + isSuspend = source.isSuspend + returnType = source.returnType.simple("ImportedInterfaceAdapter.returnType").concrete(importInfo) + }.apply { + parent = file + val receiver = addValueParameter( + DownwardFunctionSignature.Extra.ReceiverInstance.asString(), + receiverType + ) + val params = regularParams.map { param -> + addValueParameter( + param.name.asString(), + param.type.simple("ImportedInterfaceAdapter.parameterType").concrete(importInfo) + ) + } + body = DeclarationIrBuilder(context.plugin, symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).irBlockBody { + val call = irCall(helper ?: target) + if (helper != null) { + source.parentAsClass.thisReceiver!!.type + .simple("ImportedInterfaceAdapter.helperType") + .arguments + .firstOrNull() + ?.typeOrNull + ?.let { typeArgument -> + call.typeArguments[0] = typeArgument + .simple("ImportedInterfaceAdapter.helperTypeArgument") + .concrete(importInfo) + } + call.arguments[0] = irGet(receiver) + params.forEachIndexed { index, param -> + call.arguments[index + 1] = irGet(param) + } + } else { + call.dispatchReceiver = irGet(receiver) + targetRegularParams.forEachIndexed { index, _ -> + call.arguments[targetRegularParamSlots[index].index] = irGet(params[index]) + } + } + +irReturn(call) + } + file.declarations += this + }.also { + importedAdapter = it + } +} + +private tailrec fun IrSimpleFunction.normalizedImportedTarget(): IrSimpleFunction { + if (!isFakeOverride) return this + val next = overriddenSymbols + .map { it.owner } + .filterIsInstance() + .firstOrNull { !it.isFakeOverride } + ?: return this + return next.normalizedImportedTarget() +} + +private fun IrSimpleFunction.importedRuntimeHelper(context: KneeContext): IrSimpleFunction? { + val owner = parentClassOrNull?.fqNameWhenAvailable?.asString() + return when { + name.asString() == "collect" && owner == "kotlinx.coroutines.flow.Flow" -> { + context.symbols.functions(RuntimeIds.kneeImportedCollectFlow).single().owner + } + name.asString() == "collect" && owner in setOf( + "kotlinx.coroutines.flow.SharedFlow", + "kotlinx.coroutines.flow.StateFlow", + "kotlinx.coroutines.flow.MutableSharedFlow", + "kotlinx.coroutines.flow.MutableStateFlow", + ) -> { + context.symbols.functions(RuntimeIds.kneeImportedCollectSharedFlow).single().owner + } + name.asString() == "emit" && owner in setOf( + "kotlinx.coroutines.flow.FlowCollector", + "kotlinx.coroutines.flow.MutableSharedFlow", + "kotlinx.coroutines.flow.MutableStateFlow", + ) -> { + context.symbols.functions(RuntimeIds.kneeImportedEmitFlowCollector).single().owner + } + else -> null + } +} diff --git a/knee-compiler-plugin/src/main/kotlin/functions/UpwardFunctionSignature.kt b/knee-compiler-plugin/src/main/kotlin/functions/UpwardFunctionSignature.kt index a2e6edf..41a4724 100644 --- a/knee-compiler-plugin/src/main/kotlin/functions/UpwardFunctionSignature.kt +++ b/knee-compiler-plugin/src/main/kotlin/functions/UpwardFunctionSignature.kt @@ -15,6 +15,7 @@ import io.deepmedia.tools.knee.plugin.compiler.jni.JniSignature import io.deepmedia.tools.knee.plugin.compiler.jni.JniType import io.deepmedia.tools.knee.plugin.compiler.utils.* import io.deepmedia.tools.knee.plugin.compiler.symbols.RuntimeIds.KneeSuspendInvocation +import org.jetbrains.kotlin.ir.declarations.IrParameterKind import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.IrValueParameter import org.jetbrains.kotlin.ir.symbols.IrClassSymbol @@ -78,7 +79,7 @@ class UpwardFunctionSignature( } } - val regularParameters: List> = source.valueParameters.map { + val regularParameters: List> = source.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }.map { it.name to mapper.get(it.type.simple("UpwardSignature.regularParams").concrete(kind.importInfo), it) } @@ -89,7 +90,7 @@ class UpwardFunctionSignature( ) { @Suppress("DefaultLocale") fun name(includeAncestors: Boolean): Name { - val suffix = source.valueParameters.makeFunctionNameDisambiguationSuffix() + val suffix = source.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }.makeFunctionNameDisambiguationSuffix() fun mapper(name: String): String = "_\$" + when { source.isGetter -> "get${source.correspondingPropertySymbol!!.owner.name.asString().capitalizeAsciiOnly()}" diff --git a/knee-compiler-plugin/src/main/kotlin/functions/UpwardFunctionsIr.kt b/knee-compiler-plugin/src/main/kotlin/functions/UpwardFunctionsIr.kt index e229d9e..bb75dde 100644 --- a/knee-compiler-plugin/src/main/kotlin/functions/UpwardFunctionsIr.kt +++ b/knee-compiler-plugin/src/main/kotlin/functions/UpwardFunctionsIr.kt @@ -5,6 +5,7 @@ import io.deepmedia.tools.knee.plugin.compiler.jni.JniType import io.deepmedia.tools.knee.plugin.compiler.codec.IrCodecContext import io.deepmedia.tools.knee.plugin.compiler.symbols.RuntimeIds.callStaticMethod import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.IrParameterKind import org.jetbrains.kotlin.ir.declarations.IrValueParameter import org.jetbrains.kotlin.ir.declarations.IrVariable import org.jetbrains.kotlin.ir.expressions.IrExpression @@ -31,18 +32,21 @@ object UpwardFunctionsIr { returnJniType: JniType, suspendInvoker: IrValueParameter? = null ): IrExpression { - val logPrefix = "ReverseFunctionsIr.irInvoke(${codecContext.functionSymbol!!.owner.fqNameWhenAvailable})" + val logPrefix = + "ReverseFunctionsIr.irInvoke(${codecContext.functionSymbol!!.owner.fqNameWhenAvailable})" // Take care of prefixes codecContext.logger.injectLog(this, "$logPrefix START") val prefixInputs = signature.extraParameters.map { (param, codec) -> codecContext.logger.injectLog(this, "$logPrefix ENCODING prefix $param with $codec") with(codec) { - irEncode(codecContext, local = when (param) { - UpwardFunctionSignature.Extra.Receiver -> jreceiver - UpwardFunctionSignature.Extra.SuspendInvoker -> suspendInvoker!! - else -> error("Unexpected prefix parameter: $param") - }) + irEncode( + codecContext, local = when (param) { + UpwardFunctionSignature.Extra.Receiver -> jreceiver + UpwardFunctionSignature.Extra.SuspendInvoker -> suspendInvoker!! + else -> error("Unexpected prefix parameter: $param") + } + ) } } @@ -60,28 +64,35 @@ object UpwardFunctionsIr { return irCall( symbols.functions(function).single() ).apply { - extensionReceiver = irGet(codecContext.environment) - putValueArgument(0, irGet(jmethodOwner)) - putValueArgument(1, irGet(jmethod)) - putValueArgument(2, irVararg( + val extensionIndex = symbol.owner.parameters.indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } + val ownerIndex = symbol.owner.parameters.indexOfFirst { it.name.asString() == "jclass" } + .takeIf { it >= 0 } + ?: symbol.owner.parameters.indexOfFirst { it.name.asString() == "jobjectOrJClass" } + val methodIndex = symbol.owner.parameters.indexOfFirst { it.name.asString() == "method" } + val argsIndex = symbol.owner.parameters.indexOfFirst { it.name.asString() == "args" } + arguments[extensionIndex] = irGet(codecContext.environment) + arguments[ownerIndex] = irGet(jmethodOwner) + arguments[methodIndex] = irGet(jmethod) + arguments[argsIndex] = irVararg( elementType = symbols.builtIns.anyType.makeNullable(), values = prefixInputs + mappedInputs - )) + ) } } - private val JniType.nameOfCallMethodFunction: String get() { - return when (this) { - is JniType.Void -> "Void" - is JniType.Object -> "Object" - is JniType.Int -> "Int" - is JniType.BooleanAsUByte -> "Boolean" - is JniType.Float -> "Float" - is JniType.Double -> "Double" - is JniType.Byte -> "Byte" - is JniType.Long -> "Long" + private val JniType.nameOfCallMethodFunction: String + get() { + return when (this) { + is JniType.Void -> "Void" + is JniType.Object -> "Object" + is JniType.Int -> "Int" + is JniType.BooleanAsUByte -> "Boolean" + is JniType.Float -> "Float" + is JniType.Double -> "Double" + is JniType.Byte -> "Byte" + is JniType.Long -> "Long" + } } - } /** @@ -93,13 +104,17 @@ object UpwardFunctionsIr { codecContext: IrCodecContext, suspendToken: Boolean = false ): IrExpression { - val logPrefix = "ReverseFunctionsIr.irReceive(${codecContext.functionSymbol!!.owner.fqNameWhenAvailable})" + val logPrefix = + "ReverseFunctionsIr.irReceive(${codecContext.functionSymbol!!.owner.fqNameWhenAvailable})" val returnType = if (suspendToken) signature.suspendResult else signature.result if (!returnType.needsIrConversion) return rawValue return with(returnType) { - codecContext.logger.injectLog(this@irReceive, "$logPrefix DECODING return type with $returnType") + codecContext.logger.injectLog( + this@irReceive, + "$logPrefix DECODING return type with $returnType" + ) irDecode(codecContext, irTemporary(rawValue, "result")) } } -} \ No newline at end of file +} diff --git a/knee-compiler-plugin/src/main/kotlin/import/ImportInfo.kt b/knee-compiler-plugin/src/main/kotlin/import/ImportInfo.kt index ffe7f79..0eed5af 100644 --- a/knee-compiler-plugin/src/main/kotlin/import/ImportInfo.kt +++ b/knee-compiler-plugin/src/main/kotlin/import/ImportInfo.kt @@ -3,12 +3,13 @@ package io.deepmedia.tools.knee.plugin.compiler.import import com.squareup.kotlinpoet.TypeVariableName import io.deepmedia.tools.knee.plugin.compiler.utils.asTypeName import io.deepmedia.tools.knee.plugin.compiler.utils.simple -import org.jetbrains.kotlin.backend.common.lower.parents -import org.jetbrains.kotlin.ir.IrBuiltIns -import org.jetbrains.kotlin.ir.builders.declarations.buildClass -import org.jetbrains.kotlin.ir.declarations.* -import org.jetbrains.kotlin.ir.types.* -import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.types.IrSimpleType +import org.jetbrains.kotlin.ir.types.IrTypeSubstitutor +import org.jetbrains.kotlin.ir.types.classOrNull +import org.jetbrains.kotlin.ir.types.typeOrNull +import org.jetbrains.kotlin.ir.util.file class ImportInfo( val type: IrSimpleType, diff --git a/knee-compiler-plugin/src/main/kotlin/metadata/ModuleMetadata.kt b/knee-compiler-plugin/src/main/kotlin/metadata/ModuleMetadata.kt index 07025b1..a1c5efb 100644 --- a/knee-compiler-plugin/src/main/kotlin/metadata/ModuleMetadata.kt +++ b/knee-compiler-plugin/src/main/kotlin/metadata/ModuleMetadata.kt @@ -43,9 +43,11 @@ data class ModuleMetadata private constructor( fun read(module: IrClass, json: Json): ModuleMetadata? { @Suppress("UNCHECKED_CAST") val encoded = module.getAnnotation(AnnotationIds.KneeMetadata.asSingleFqName()) - ?.getValueArgument(0) - ?.let { it as? IrConst } - ?.value ?: return null + ?.arguments[0] + ?.let { it as? IrConst } + ?.value + ?.let { it as? String } + ?: return null return json.decodeFromString(encoded) } } @@ -53,7 +55,7 @@ data class ModuleMetadata private constructor( fun write(module: IrClass, context: KneeContext) { val existingMetadataAnnotation = module.getAnnotation(AnnotationIds.KneeMetadata.asSingleFqName()) check(existingMetadataAnnotation == null) { - "Module $module should not be annotated by @KneeMetadata(${existingMetadataAnnotation?.getValueArgument(0)?.dumpKotlinLike()})" + "Module $module should not be annotated by @KneeMetadata(${existingMetadataAnnotation?.arguments[0]?.dumpKotlinLike()})" } val metadataString = try { @@ -68,7 +70,7 @@ data class ModuleMetadata private constructor( annotations = listOf(with(DeclarationIrBuilder(context.plugin, module.symbol)) { val metadataConstructor = context.symbols.klass(AnnotationIds.KneeMetadata).constructors.single() irCallConstructor(metadataConstructor, emptyList()).apply { - putValueArgument(0, irString(metadataString)) + arguments[0] = irString(metadataString) } }) ) diff --git a/knee-compiler-plugin/src/main/kotlin/services/KneeComponentRegistrar.kt b/knee-compiler-plugin/src/main/kotlin/services/KneeComponentRegistrar.kt index 61f7301..ded7dc8 100644 --- a/knee-compiler-plugin/src/main/kotlin/services/KneeComponentRegistrar.kt +++ b/knee-compiler-plugin/src/main/kotlin/services/KneeComponentRegistrar.kt @@ -10,7 +10,9 @@ import org.jetbrains.kotlin.config.CompilerConfiguration import java.io.File @OptIn(ExperimentalCompilerApi::class) -class KneeComponentRegistrar : CompilerPluginRegistrar() { +class KneeComponentRegistrar() : CompilerPluginRegistrar() { + override val pluginId: String = "knee-component-registrar" + override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { if (configuration[KneeCommandLineProcessor.KneeEnabled] == false) return val logs = configuration[CommonConfigurationKeys.MESSAGE_COLLECTOR_KEY]!! diff --git a/knee-compiler-plugin/src/main/kotlin/symbols/KneeSymbols.kt b/knee-compiler-plugin/src/main/kotlin/symbols/KneeSymbols.kt index 963add1..8e46c34 100644 --- a/knee-compiler-plugin/src/main/kotlin/symbols/KneeSymbols.kt +++ b/knee-compiler-plugin/src/main/kotlin/symbols/KneeSymbols.kt @@ -10,7 +10,7 @@ import org.jetbrains.kotlin.name.CallableId import org.jetbrains.kotlin.name.ClassId -class KneeSymbols(private val plugin: IrPluginContext) { +class KneeSymbols (private val plugin: IrPluginContext) { val builtIns: IrBuiltIns get() = plugin.irBuiltIns diff --git a/knee-compiler-plugin/src/main/kotlin/symbols/SymbolIds.kt b/knee-compiler-plugin/src/main/kotlin/symbols/SymbolIds.kt index ea7db25..fff45c5 100644 --- a/knee-compiler-plugin/src/main/kotlin/symbols/SymbolIds.kt +++ b/knee-compiler-plugin/src/main/kotlin/symbols/SymbolIds.kt @@ -89,7 +89,11 @@ object RuntimeIds { val KneeSuspendInvocation = FqName("io.deepmedia.tools.knee.runtime.compiler.KneeSuspendInvocation") val kneeInvokeJvmSuspend = CallableId(PackageNames.runtimeCompiler, Name.identifier("kneeInvokeJvmSuspend")) val kneeInvokeKnSuspend = CallableId(PackageNames.runtimeCompiler, Name.identifier("kneeInvokeKnSuspend")) + val kneeGlobalize = CallableId(PackageNames.runtimeCompiler, Name.identifier("kneeGlobalize")) + val kneeImportedCollectFlow = CallableId(PackageNames.runtimeCompiler, Name.identifier("kneeImportedCollectFlow")) + val kneeImportedCollectSharedFlow = CallableId(PackageNames.runtimeCompiler, Name.identifier("kneeImportedCollectSharedFlow")) + val kneeImportedEmitFlowCollector = CallableId(PackageNames.runtimeCompiler, Name.identifier("kneeImportedEmitFlowCollector")) val JvmInterfaceWrapper = ClassId(PackageNames.runtimeCompiler, Name.identifier("JvmInterfaceWrapper")) val rethrowNativeException = CallableId(PackageNames.runtimeCompiler, Name.identifier("rethrowNativeException")) val SerializableException = ClassId(PackageNames.runtimeCompiler, Name.identifier("SerializableException")) -} \ No newline at end of file +} diff --git a/knee-compiler-plugin/src/main/kotlin/utils/IrUtils.kt b/knee-compiler-plugin/src/main/kotlin/utils/IrUtils.kt index 1a4e60e..d1e396e 100644 --- a/knee-compiler-plugin/src/main/kotlin/utils/IrUtils.kt +++ b/knee-compiler-plugin/src/main/kotlin/utils/IrUtils.kt @@ -31,8 +31,8 @@ fun IrDeclaration.isPartOf(module: IrModuleFragment): Boolean { fun IrFunction.requireNotComplex(description: Any, allowSuspend: Boolean = false) { require(typeParameters.isEmpty()) { "$description can't have type parameters." } - require(extensionReceiverParameter == null) { "$description can't be an extension function." } - require(contextReceiverParametersCount == 0) { "$description can't have context receivers." } + require(parameters.firstOrNull { it.kind == IrParameterKind.ExtensionReceiver } == null) { "$description can't be an extension function." } + require(parameters.count { it.kind == IrParameterKind.Context } == 0) { "$description can't have context receivers." } require(allowSuspend || !isSuspend) { "$description can't be suspend." } require(!isExpect) { "$description can't be an expect function, please annotate the actual function instead." } } @@ -86,6 +86,7 @@ fun IrDeclarationContainer.addSimpleProperty( backingField = factory.buildField { isStatic = parent is IrFile || (parent is IrDeclaration && parent.isFileClass) // very important origin = IrDeclarationOrigin.PROPERTY_BACKING_FIELD + visibility = DescriptorVisibilities.PRIVATE this.name = name this.type = type }.apply { @@ -173,13 +174,16 @@ fun irLambda( return IrFunctionExpressionImpl( startOffset = SYNTHETIC_OFFSET, endOffset = SYNTHETIC_OFFSET, - type = run { - when (suspend) { - false -> context.symbols.klass(KotlinIds.FunctionX(lambda.valueParameters.size)) - true -> context.symbols.klass(KotlinIds.SuspendFunctionX(lambda.valueParameters.size)) - // true -> context.irBuiltIns.suspendFunctionN(lambda.valueParameters.size) - }.typeWith(*lambda.valueParameters.map { it.type }.toTypedArray(), lambda.returnType) - }, + type = lambda.parameters + .filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + .run { + val arity = size + val klass = when (suspend) { + false -> context.symbols.klass(KotlinIds.FunctionX(arity)) + true -> context.symbols.klass(KotlinIds.SuspendFunctionX(arity)) + } + klass.typeWith(*map { it.type }.toTypedArray(), lambda.returnType) + }, origin = IrStatementOrigin.LAMBDA, function = lambda ) @@ -188,7 +192,7 @@ fun irLambda( fun IrBuilderWithScope.irError(symbols: KneeSymbols, message: String): IrExpression { val error = symbols.functions(KotlinIds.error).single() return irCall(error).apply { - putValueArgument(0, irString(message)) + arguments[0] = irString(message) } } diff --git a/knee-compiler-plugin/src/main/kotlin/utils/NameUtils.kt b/knee-compiler-plugin/src/main/kotlin/utils/NameUtils.kt index 3b14bc8..99e51eb 100644 --- a/knee-compiler-plugin/src/main/kotlin/utils/NameUtils.kt +++ b/knee-compiler-plugin/src/main/kotlin/utils/NameUtils.kt @@ -69,7 +69,7 @@ val IrDeclarationWithName.codegenName get(): Name { ?: getAnnotation(AnnotationIds.KneeInterface) ?: return name val a = e.getValueArgument(Name.identifier("name")) ?: return name - val str = (a as IrConst).value.takeIf { it.isNotEmpty() } ?: return name + val str = (a as IrConst).value?.let { it as? String }?.takeIf { it.isNotEmpty() } ?: return name return Name.identifier(str) } return name diff --git a/knee-compiler-plugin/src/main/kotlin/utils/PoetUtils.kt b/knee-compiler-plugin/src/main/kotlin/utils/PoetUtils.kt index 0b96e73..7ea6a3f 100644 --- a/knee-compiler-plugin/src/main/kotlin/utils/PoetUtils.kt +++ b/knee-compiler-plugin/src/main/kotlin/utils/PoetUtils.kt @@ -26,6 +26,7 @@ fun IrClass.asTypeSpec(rename: ((String) -> String)? = null): TypeSpec.Builder { isCompanion -> TypeSpec.companionObjectBuilder(if (name == "Companion") null else name) else -> TypeSpec.objectBuilder(name) } + ClassKind.INTERFACE -> TypeSpec.interfaceBuilder(name) ClassKind.ANNOTATION_CLASS -> TypeSpec.annotationBuilder(name) ClassKind.ENUM_ENTRY -> error("Enum entries ($this) can't become a TypeSpec.") @@ -47,6 +48,7 @@ fun IrSimpleType.asTypeName(alreadyDescribedTypeParameters: MutableSet { asClassTypeName(alreadyDescribedTypeParameters) } + is IrTypeParameterSymbol -> TypeVariableName( name = s.owner.name.asString(), bounds = when { @@ -56,6 +58,7 @@ fun IrSimpleType.asTypeName(alreadyDescribedTypeParameters: MutableSet error("Unexpected classifier: $s") } } @@ -63,7 +66,8 @@ fun IrSimpleType.asTypeName(alreadyDescribedTypeParameters: MutableSet): TypeName { return when (this) { is IrTypeProjection -> { - val simpleType = checkNotNull(type as? IrSimpleType) { "IrTypeArgument.type not a simple type: $type" } + val simpleType = + checkNotNull(type as? IrSimpleType) { "IrTypeArgument.type not a simple type: $type" } val invariant = simpleType.asTypeName(alreadyDescribedTypeParameters) when (this.variance) { Variance.INVARIANT -> invariant @@ -71,6 +75,7 @@ private fun IrTypeArgument.asTypeName(alreadyDescribedTypeParameters: MutableSet Variance.OUT_VARIANCE -> WildcardTypeName.producerOf(outType = invariant) } } + is IrStarProjection -> STAR else -> error("Should not happen? ${this::class.simpleName}") } @@ -83,7 +88,11 @@ private fun IrSimpleType.asClassTypeName(alreadyDescribedTypeParameters: Mutable val className = ClassName.bestGuess(fqName.asString()) return when (arguments.isEmpty()) { true -> className - else -> className.parameterizedBy(arguments.map { it.asTypeName(alreadyDescribedTypeParameters) }) + else -> className.parameterizedBy(arguments.map { + it.asTypeName( + alreadyDescribedTypeParameters + ) + }) }.copy(nullable = isNullable()) } @@ -91,7 +100,8 @@ private fun IrSimpleType.asClassTypeName(alreadyDescribedTypeParameters: Mutable fun IrProperty.asPropertySpec(typeMapper: (IrSimpleType) -> IrSimpleType = { it }): PropertySpec.Builder { // NOTE: Could also add kmodifiers from visibility and modality val type = requireNotNull(backingField?.type ?: getter?.returnType) - val simpleType = checkNotNull(type as? IrSimpleType) { "IrProperty.type not a simple type: $type" } + val simpleType = + checkNotNull(type as? IrSimpleType) { "IrProperty.type not a simple type: $type" } val mappedType = typeMapper(simpleType) return PropertySpec.builder(codegenName.asString(), mappedType.asTypeName()) .mutable(isVar) @@ -117,6 +127,7 @@ fun DescriptorVisibility.asModifier(): KModifier { ?.visibility ?.asModifier() ?: KModifier.PRIVATE } + else -> KModifier.INTERNAL } // K1 @@ -135,36 +146,44 @@ fun DescriptorVisibility.asModifier(): KModifier { } */ } -val TypeName.simpleName: String get() { - return when (this) { - is ClassName -> simpleName - is ParameterizedTypeName -> rawType.simpleName - is Dynamic -> error("Not possible") // JS dynamic type - is TypeVariableName -> error("Not possible") // describes generic named type parameter e.g. 'T : String' - is LambdaTypeName -> error("Not possible") // describes lambdas - is WildcardTypeName -> error("Not possible") // describes out String, in String, * ... +val TypeName.simpleName: String + get() { + return when (this) { + is ClassName -> simpleName + is ParameterizedTypeName -> rawType.simpleName + is Dynamic -> error("Not possible") // JS dynamic type + is TypeVariableName -> error("Not possible") // describes generic named type parameter e.g. 'T : String' + is LambdaTypeName -> error("Not possible") // describes lambdas + is WildcardTypeName -> error("Not possible") // describes out String, in String, * ... + } } -} -val TypeName.canonicalName: String get() { - return when (this) { - is ClassName -> canonicalName - is ParameterizedTypeName -> rawType.canonicalName - is Dynamic, is LambdaTypeName, is TypeVariableName, is WildcardTypeName -> error("Not possible: ${this}") +val TypeName.canonicalName: String + get() { + return when (this) { + is ClassName -> canonicalName + is ParameterizedTypeName -> rawType.canonicalName + is Dynamic, is LambdaTypeName, is TypeVariableName, is WildcardTypeName -> error("Not possible: ${this}") + } } -} // Wrt canonicalName, this handles TypeVariableName. // That can appear when creating codegen function for generic interfaces, because we use // unsubstituted types in the base interface clone -val TypeName.disambiguationName: String get() { - return when (this) { - is ClassName -> canonicalName - is ParameterizedTypeName -> rawType.canonicalName - is TypeVariableName -> "${if (isReified) "reified " else ""}$variance $name : ${bounds.map { it.disambiguationName }}" - is Dynamic, is LambdaTypeName, is WildcardTypeName -> error("Not possible: ${this}") +val TypeName.disambiguationName: String + get() { + return when (this) { + is ClassName -> canonicalName + is ParameterizedTypeName -> rawType.canonicalName + + typeArguments.joinToString( + separator = ",", + prefix = "<", + postfix = ">", + ) { it.disambiguationName } + is TypeVariableName -> "${if (isReified) "reified " else ""}$variance $name : ${bounds.map { it.disambiguationName }}" + is Dynamic, is LambdaTypeName, is WildcardTypeName -> error("Not possible: ${this}") + } } -} /* val TypeName.packageName: String get() { return when (this) { @@ -195,25 +214,27 @@ fun TypeName.copy( ): TypeName { return when (this) { is ClassName -> this - is ParameterizedTypeName -> when { + is ParameterizedTypeName -> when { clearGenerics -> rawType wildcardGenerics -> copy(typeArguments = List(typeArguments.size) { STAR }) else -> this } + is Dynamic, is LambdaTypeName, is TypeVariableName, is WildcardTypeName -> error("Not possible") } } // We only support const kinds. fun IrValueParameter.defaultValueForCodegen(functionExpects: List = emptyList()): CodeBlock? { - val expression = (defaultValueFromThisOrSupertypes ?: defaultValueFromExpect(functionExpects) ?: return null).expression - if (expression is IrConst<*>) { + val expression = (defaultValueFromThisOrSupertypes ?: defaultValueFromExpect(functionExpects) + ?: return null).expression + if (expression is IrConst) { return when (val kind = expression.kind) { is IrConstKind.Null -> CodeBlock.of("null") - is IrConstKind.String -> CodeBlock.of("%S", kind.valueOf(expression)) - is IrConstKind.Float -> CodeBlock.of("%LF", kind.valueOf(expression)) - is IrConstKind.Long -> CodeBlock.of("%LL", kind.valueOf(expression)) - else -> CodeBlock.of("%L", kind.valueOf(expression)) + is IrConstKind.String -> CodeBlock.of("%S", expression.value) + is IrConstKind.Float -> CodeBlock.of("%LF", expression.value) + is IrConstKind.Long -> CodeBlock.of("%LL", expression.value) + else -> CodeBlock.of("%L", expression.value) // is IrConstKind.Boolean -> CodeBlock.of(kind.valueOf(expression).toString()) // is IrConstKind.Int -> CodeBlock.of(kind.valueOf(expression).toString()) // is IrConstKind.Double -> CodeBlock.of(kind.valueOf(expression).toString()) @@ -228,20 +249,29 @@ fun IrValueParameter.defaultValueForCodegen(functionExpects: List): IrExpressionBody? { return functionExpects.asSequence() .filterIsInstance() - .mapNotNull { it.valueParameters.firstOrNull { it.name == name } } + .mapNotNull { + it.parameters + .filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + .firstOrNull { it.name == name } + } .firstNotNullOfOrNull { it.defaultValueFromThisOrSupertypes } } diff --git a/knee-gradle-plugin/src/main/kotlin/KneePlugin.kt b/knee-gradle-plugin/src/main/kotlin/KneePlugin.kt index ec71b0f..a931771 100644 --- a/knee-gradle-plugin/src/main/kotlin/KneePlugin.kt +++ b/knee-gradle-plugin/src/main/kotlin/KneePlugin.kt @@ -17,6 +17,8 @@ import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType import org.jetbrains.kotlin.konan.target.KonanTarget +import java.util.Locale +import java.util.Locale.getDefault @Suppress("unused") class KneePlugin : KotlinCompilerPluginSupportPlugin { @@ -139,7 +141,13 @@ class KneePlugin : KotlinCompilerPluginSupportPlugin { // TODO: if there isn't, model dependencies separately. There's no reason why something like compileDebugKotlinAndroid // should LINK the binaries. It should only compile the source code. val androidBuildType = buildType.toString().lowercase() - target.tasks.named("pre${androidBuildType.capitalize()}Build").configure { + target.tasks.named("pre${ + androidBuildType.replaceFirstChar { + if (it.isLowerCase()) + it.titlecase(getDefault()) + else it.toString() + } + }Build").configure { knee.log("[performConnection/3] [$buildType] task $name now depends on binary tasks ${binaries.map { it.linkTaskName }}") dependsOn(*binaries.map { it.linkTaskName }.toTypedArray()) } diff --git a/knee-gradle-plugin/src/main/kotlin/tasks/UnpackageCodegenSources.kt b/knee-gradle-plugin/src/main/kotlin/tasks/UnpackageCodegenSources.kt index efdb7cf..ee5804f 100644 --- a/knee-gradle-plugin/src/main/kotlin/tasks/UnpackageCodegenSources.kt +++ b/knee-gradle-plugin/src/main/kotlin/tasks/UnpackageCodegenSources.kt @@ -9,7 +9,8 @@ import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputDirectory import javax.inject.Inject -open class UnpackageCodegenSources @Inject constructor(objects: ObjectFactory, layout: ProjectLayout) : Copy() { +abstract class UnpackageCodegenSources @Inject constructor(objects: ObjectFactory, layout: ProjectLayout) : Copy() { + @get:InputFiles val codegenFiles: ConfigurableFileCollection = objects.fileCollection() diff --git a/knee-runtime/src/backendMain/kotlin/compiler/ImportedFlowCalls.kt b/knee-runtime/src/backendMain/kotlin/compiler/ImportedFlowCalls.kt new file mode 100644 index 0000000..05168f8 --- /dev/null +++ b/knee-runtime/src/backendMain/kotlin/compiler/ImportedFlowCalls.kt @@ -0,0 +1,32 @@ +package io.deepmedia.tools.knee.runtime.compiler + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.SharedFlow + +@Suppress("unused") +@PublishedApi +internal suspend fun kneeImportedCollectFlow( + flow: Flow, + collector: FlowCollector, +) { + flow.collect(collector) +} + +@Suppress("unused") +@PublishedApi +internal suspend fun kneeImportedCollectSharedFlow( + flow: SharedFlow, + collector: FlowCollector, +): Nothing { + return flow.collect(collector) +} + +@Suppress("unused") +@PublishedApi +internal suspend fun kneeImportedEmitFlowCollector( + collector: FlowCollector, + value: T, +) { + collector.emit(value) +} diff --git a/knee-runtime/src/backendMain/kotlin/compiler/JvmInterfaceWrapper.kt b/knee-runtime/src/backendMain/kotlin/compiler/JvmInterfaceWrapper.kt index b95f68d..e6540a4 100644 --- a/knee-runtime/src/backendMain/kotlin/compiler/JvmInterfaceWrapper.kt +++ b/knee-runtime/src/backendMain/kotlin/compiler/JvmInterfaceWrapper.kt @@ -17,16 +17,9 @@ internal open class JvmInterfaceWrapper( // accessed by IR val virtualMachine = environment.javaVM - // needed to return the wrapped object to JVM - // TODO: the deleteLocalRef below creates the warning: Attempt to remove non-JNI local reference - // When we, for example, pass a jobject to native and create an interface out of it. Concretely we are calling - // deleteLocalRef on the jobject from within the method, which is not needed. - // Same for almost all other usages of deleteLocalRef we do: it should be called by who creates - // We need to pass more information to codecs, to understand whether the resource should be released or not - // or maybe no resource needs to be released at all - val jvmInterfaceObject = environment.newGlobalRef(wrapped).also { - environment.deleteLocalRef(wrapped) - } + // Keep a global ref for cross-boundary callbacks. Do not delete `wrapped` here: + // the wrapper does not own the incoming JNI reference and it might already be global. + val jvmInterfaceObject = environment.newGlobalRef(wrapped) private val jvmInterfaceCleaner = createCleaner(this.virtualMachine to this.jvmInterfaceObject) { (virtualMachine, it) -> // looking at K/N code, looks like cleaner blocks are invoked on a special cleaner worker that should be long-lived, @@ -50,6 +43,10 @@ internal open class JvmInterfaceWrapper( toString = MethodIds.get(environment, interfaceFqn, "toString", "()Ljava/lang/String;", false, jclass) } + // needed to call methods using env.callStatic***Method() + // accessed from IR + val methodOwnerClass = ClassIds.get(environment, methodOwnerFqn) + private val methodIds: Map = run { val count = methodsAndSignatures.size / 2 (0 until count).associate { @@ -59,10 +56,6 @@ internal open class JvmInterfaceWrapper( } } - // needed to call methods using env.callStatic***Method() - // accessed from IR - val methodOwnerClass = ClassIds.get(environment, methodOwnerFqn) - // key is name::signature fun method(key: String): jmethodID { return checkNotNull(methodIds[key]) { "Method $key not found. Available: ${methodIds.keys}" } @@ -84,4 +77,4 @@ internal open class JvmInterfaceWrapper( decodeString(it, jstring!!) } } -} \ No newline at end of file +} diff --git a/knee-runtime/src/backendMain/kotlin/compiler/Suspend.kn.kt b/knee-runtime/src/backendMain/kotlin/compiler/Suspend.kn.kt index 922e060..fa62343 100644 --- a/knee-runtime/src/backendMain/kotlin/compiler/Suspend.kn.kt +++ b/knee-runtime/src/backendMain/kotlin/compiler/Suspend.kn.kt @@ -171,19 +171,14 @@ private class KneeSuspendInvocation( @Suppress("unused") internal suspend fun kneeInvokeKnSuspend( virtualMachine: JavaVirtualMachine, - block: (JniEnvironment, Long) -> jobject, + block: (Long) -> jobject?, decoder: (JniEnvironment, Encoded) -> Decoded ): Decoded { return suspendCancellableCoroutine { cont -> // invoker must decode otherwise we might end up releasing the environment with useEnv before decode val invoker = KneeSuspendInvoker(virtualMachine, cont, decoder) - val invocation: jobject? = virtualMachine.useEnv { env -> - val weak = block(env, invoker.address) - if (invoker.completed) null else { - // TODO: review this usage of deleteLocalRef, not clear it's needed. See other usages - env.newGlobalRef(weak).also { env.deleteLocalRef(weak) } - } - } + // The block is responsible for returning a stable global ref to the JVM invocation object. + val invocation: jobject? = block(invoker.address) if (invocation != null) { cont.invokeOnCancellation { invoker.sendCancellation(invocation) @@ -200,6 +195,12 @@ internal suspend fun kneeInvokeKnSuspend( } } +@PublishedApi +@Suppress("unused") +internal fun kneeGlobalize(environment: JniEnvironment, reference: jobject): jobject { + return environment.newGlobalRef(reference).also { environment.deleteLocalRef(reference) } +} + @PublishedApi internal class KneeSuspendInvoker( private val jvm: JavaVirtualMachine, @@ -230,4 +231,3 @@ internal class KneeSuspendInvoker( jvm.useEnv { env -> env.callVoidMethod(invocation, cancelInvocationMethod) } } } - diff --git a/settings.gradle.kts b/settings.gradle.kts index c66e1bf..a488b34 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,10 +9,10 @@ pluginManagement { } plugins { - kotlin("multiplatform") version "2.0.20" apply false - kotlin("plugin.serialization") version "2.0.20" apply false - kotlin("jvm") version "2.0.20" apply false - id("io.deepmedia.tools.deployer") version "0.14.0" apply false + kotlin("multiplatform") version "2.3.0" apply false + kotlin("plugin.serialization") version "2.3.0" apply false + kotlin("jvm") version "2.3.0" apply false + id("io.deepmedia.tools.deployer") version "0.18.0" apply false } } diff --git a/tests/test-classes/build.gradle.kts b/tests/test-classes/build.gradle.kts index c8d0339..21321f4 100644 --- a/tests/test-classes/build.gradle.kts +++ b/tests/test-classes/build.gradle.kts @@ -1,8 +1,8 @@ @file:Suppress("UnstableApiUsage") plugins { - kotlin("multiplatform") version "2.0.20" - id("com.android.application") version "8.1.1" + kotlin("multiplatform") version "2.3.0" + id("com.android.application") version "8.13.0" id("io.deepmedia.tools.knee") } diff --git a/tests/test-coroutines/build.gradle.kts b/tests/test-coroutines/build.gradle.kts index 4f41edc..c800d43 100644 --- a/tests/test-coroutines/build.gradle.kts +++ b/tests/test-coroutines/build.gradle.kts @@ -1,8 +1,8 @@ @file:Suppress("UnstableApiUsage") plugins { - kotlin("multiplatform") version "2.0.20" - id("com.android.application") version "8.1.1" + kotlin("multiplatform") version "2.3.0" + id("com.android.application") version "8.13.0" id("io.deepmedia.tools.knee") } diff --git a/tests/test-imports/build.gradle.kts b/tests/test-imports/build.gradle.kts index 4f41edc..c800d43 100644 --- a/tests/test-imports/build.gradle.kts +++ b/tests/test-imports/build.gradle.kts @@ -1,8 +1,8 @@ @file:Suppress("UnstableApiUsage") plugins { - kotlin("multiplatform") version "2.0.20" - id("com.android.application") version "8.1.1" + kotlin("multiplatform") version "2.3.0" + id("com.android.application") version "8.13.0" id("io.deepmedia.tools.knee") } diff --git a/tests/test-interfaces/build.gradle.kts b/tests/test-interfaces/build.gradle.kts index 4f41edc..c800d43 100644 --- a/tests/test-interfaces/build.gradle.kts +++ b/tests/test-interfaces/build.gradle.kts @@ -1,8 +1,8 @@ @file:Suppress("UnstableApiUsage") plugins { - kotlin("multiplatform") version "2.0.20" - id("com.android.application") version "8.1.1" + kotlin("multiplatform") version "2.3.0" + id("com.android.application") version "8.13.0" id("io.deepmedia.tools.knee") } diff --git a/tests/test-interfaces/src/backendMain/kotlin/Definintions.kt b/tests/test-interfaces/src/backendMain/kotlin/Definintions.kt index 530c05f..493a8df 100644 --- a/tests/test-interfaces/src/backendMain/kotlin/Definintions.kt +++ b/tests/test-interfaces/src/backendMain/kotlin/Definintions.kt @@ -71,9 +71,11 @@ fun invokeCallbackSetCounter(callback: Callback, value: UInt) { callback.counter = value } +@KneeClass class Outer { @KneeInterface interface Inner { + @Knee var value: Int } } diff --git a/tests/test-misc/build.gradle.kts b/tests/test-misc/build.gradle.kts index 4f41edc..c800d43 100644 --- a/tests/test-misc/build.gradle.kts +++ b/tests/test-misc/build.gradle.kts @@ -1,8 +1,8 @@ @file:Suppress("UnstableApiUsage") plugins { - kotlin("multiplatform") version "2.0.20" - id("com.android.application") version "8.1.1" + kotlin("multiplatform") version "2.3.0" + id("com.android.application") version "8.13.0" id("io.deepmedia.tools.knee") } diff --git a/tests/test-primitives/build.gradle.kts b/tests/test-primitives/build.gradle.kts index c806fa8..c724fcf 100644 --- a/tests/test-primitives/build.gradle.kts +++ b/tests/test-primitives/build.gradle.kts @@ -1,8 +1,8 @@ @file:Suppress("UnstableApiUsage") plugins { - kotlin("multiplatform") version "2.0.20" - id("com.android.application") version "8.1.1" + kotlin("multiplatform") version "2.3.0" + id("com.android.application") version "8.13.0" id("io.deepmedia.tools.knee") }