From 79fc24fa620181b80f1ca6a014e3c5d6d0d9dfae Mon Sep 17 00:00:00 2001 From: Sky233 Date: Sat, 25 Apr 2026 17:02:27 +0800 Subject: [PATCH 01/15] Update Kotlin, Gradle, MavenDeployer, Shadow versions * Update Kotlin to 2.3.0 * Update Gradle to 9.0 * Update MavenDeployer to 0.18.0 * Update Shadow to 9.4.1 --- gradle/wrapper/gradle-wrapper.properties | 2 +- knee-compiler-plugin/build.gradle.kts | 2 +- settings.gradle.kts | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) 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/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 } } From adf0c9fa6887d7710f3df30e06377cb34138de08 Mon Sep 17 00:00:00 2001 From: Sky233 Date: Sat, 25 Apr 2026 20:14:42 +0800 Subject: [PATCH 02/15] Update to Kotlin 2.0 IR API (#20) * Update IR element accessors to use `arguments`, `typeArguments`, and `parameters` instead of deprecated `valueParameters`, `putValueArgument`, etc. * Support `IrParameterKind` for filtering extension, regular, and context parameters. * Update `IrConst` usage to use type-safe value access. * Replace `IrElementVisitorVoid` with `IrVisitorVoid`. * Fix `capitalize` deprecations in Gradle plugin and compiler. * Improve IR building logic for lambdas and constructor calls to match new compiler requirements. * Update `KneeLogger` to use `InternalSymbolFinderAPI` for finding standard library functions. * Refactor `UnpackageCodegenSources` task to be an abstract class. --- .../src/main/kotlin/Classes.kt | 39 ++- .../src/main/kotlin/DownwardFunctions.kt | 293 ++++++++++++------ knee-compiler-plugin/src/main/kotlin/Enums.kt | 28 +- knee-compiler-plugin/src/main/kotlin/Init.kt | 197 ++++++++---- .../src/main/kotlin/Interfaces.kt | 47 +-- .../src/main/kotlin/UpwardFunctions.kt | 28 +- .../src/main/kotlin/codec/BufferCodecs.kt | 7 +- .../src/main/kotlin/codec/CollectionCodec.kt | 52 ++-- .../src/main/kotlin/codec/GenericCodec.kt | 8 +- .../src/main/kotlin/codec/PrimitiveCodecs.kt | 4 +- .../src/main/kotlin/codec/StringCodecs.kt | 8 +- .../src/main/kotlin/codec/UnsignedCodecs.kt | 5 +- .../src/main/kotlin/codegen/KneeCodegen.kt | 1 - .../src/main/kotlin/context/KneeContext.kt | 2 +- .../src/main/kotlin/context/KneeLogger.kt | 32 +- .../src/main/kotlin/context/KneeMapper.kt | 8 +- .../main/kotlin/export/v1/ExportAdapters.kt | 9 +- .../src/main/kotlin/export/v1/ExportFlags.kt | 2 +- .../src/main/kotlin/export/v1/ExportInfo.kt | 6 +- .../main/kotlin/export/v1/ExportedCodec1.kt | 8 +- .../main/kotlin/export/v2/ExportAdapters2.kt | 17 +- .../main/kotlin/export/v2/ExportedCodec2.kt | 14 +- .../src/main/kotlin/features/KneeCollector.kt | 2 +- .../src/main/kotlin/features/KneeEnum.kt | 4 +- .../functions/DownwardFunctionSignature.kt | 7 +- .../kotlin/functions/DownwardFunctionsIr.kt | 5 +- .../functions/UpwardFunctionSignature.kt | 5 +- .../kotlin/functions/UpwardFunctionsIr.kt | 61 ++-- .../main/kotlin/metadata/ModuleMetadata.kt | 12 +- .../src/main/kotlin/symbols/KneeSymbols.kt | 2 +- .../src/main/kotlin/utils/IrUtils.kt | 23 +- .../src/main/kotlin/utils/NameUtils.kt | 2 +- .../src/main/kotlin/utils/PoetUtils.kt | 107 ++++--- .../src/main/kotlin/KneePlugin.kt | 10 +- .../kotlin/tasks/UnpackageCodegenSources.kt | 3 +- 35 files changed, 676 insertions(+), 382 deletions(-) diff --git a/knee-compiler-plugin/src/main/kotlin/Classes.kt b/knee-compiler-plugin/src/main/kotlin/Classes.kt index 52048bc..78b66cf 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) { @@ -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..0b4eeb2 100644 --- a/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt +++ b/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt @@ -23,19 +23,29 @@ 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.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 +58,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 +100,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 +117,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() @@ -139,7 +161,12 @@ private fun KneeDownwardFunction.makeCodegen(codegen: KneeCodegen, signature: Do } 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) + is Kind.ClassConstructor -> codegen.prepareContainer( + source, + kind.importInfo, + createCompanionObject = true + ) + else -> codegen.prepareContainer(source, kind.importInfo, detectPropertyAccessors = false) } @@ -166,7 +193,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 +214,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 +222,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 +272,143 @@ 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 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, + 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 { + symbol.owner.parameters + .indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } + .also { arguments[it] = irGet(environment) } + arguments[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... + } + } + )), + 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, + 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/Enums.kt b/knee-compiler-plugin/src/main/kotlin/Enums.kt index 246dcef..ec58227 100644 --- a/knee-compiler-plugin/src/main/kotlin/Enums.kt +++ b/knee-compiler-plugin/src/main/kotlin/Enums.kt @@ -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..0decdb5 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[0] + 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,58 @@ 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}") } + return (this as 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 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..bf2c386 100644 --- a/knee-compiler-plugin/src/main/kotlin/Interfaces.kt +++ b/knee-compiler-plugin/src/main/kotlin/Interfaces.kt @@ -85,7 +85,7 @@ fun processInterface(interface_: KneeInterface, context: KneeContext, codegen: K 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)) + arguments[0] = irString(signature.jniInfo.name(false).asString() + "::" + signature.jniInfo.signature) }} } @@ -157,7 +157,7 @@ private fun KneeInterface.makeIrImplementation(context: KneeContext) { }.also { wrapperClass -> wrapperClass.parent = container wrapperClass.superTypes = listOf(sourceConcreteType, superClass.typeWith(sourceConcreteType)) - wrapperClass.createParameterDeclarations() // receiver + wrapperClass.createThisReceiverParameter() // receiver } container.addChild(wrapperClass) irProducts.add(wrapperClass) @@ -172,8 +172,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,16 +187,17 @@ 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) @@ -199,7 +206,7 @@ private fun KneeInterface.makeIrImplementationContents(context: KneeContext) { irString(signature.jniInfo.signature) ) } - )) + ) } context.log.injectLog(this, "Called super constructor, init self") +IrInstanceInitializerCallImpl(startOffset, endOffset, irImplementation.symbol, context.symbols.builtIns.unitType) @@ -253,9 +260,9 @@ class InterfaceCodec( */ 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) } } @@ -269,10 +276,10 @@ class InterfaceCodec( 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(), @@ -282,11 +289,11 @@ class InterfaceCodec( // 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 + arguments[0] = irGet(irContext.environment) // environment + arguments[1] = irGet(jni) // jobject }) } - )) + ) } } diff --git a/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt b/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt index 6ad1e13..d7be3e0 100644 --- a/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt +++ b/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt @@ -140,14 +140,18 @@ private fun KneeUpwardFunction.makeIr(context: KneeContext, signature: UpwardFun 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( + symbol.owner.parameters + .indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } + .also { + arguments[it] = kind.parent.irGetVirtualMachine(this@irBlockBody) + } + typeArguments[0] = signature.result.localIrType + arguments[0] = irLambda( context = context, parent = parent, content = { lambda -> @@ -161,16 +165,16 @@ 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 -> + typeArguments[0] = signature.result.encodedType.knOrNull ?: context.symbols.builtIns.unitType + typeArguments[1] = signature.result.localIrType + arguments[0] = kind.parent.irGetVirtualMachine(this@irBlockBody) + arguments[1] = irLambda(context, parent) { lambda -> val env = lambda.addValueParameter("_env", envType) val invoker = lambda.addValueParameter("_invoker", context.symbols.builtIns.longType) lambda.returnType = signature.suspendResult.localIrType @@ -181,8 +185,8 @@ private fun KneeUpwardFunction.makeIr(context: KneeContext, signature: UpwardFun context.log.injectLog(this@irBlockBody, "$logPrefix received the invocation token") +irReturn(irReceive(raw, signature, codecContext, suspendToken = true)) } - }) - putValueArgument(2, irLambda(context, parent) { lambda -> + } + 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,7 +195,7 @@ 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)) } - }) + } }) } } 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..1433ea1 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,10 @@ 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)) + symbol.owner.parameters + .indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } + .also { arguments[it] = irGet(irContext.environment) } + arguments[0] = irGet(jni) } } @@ -170,8 +180,10 @@ 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)) + symbol.owner.parameters + .indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } + .also { arguments[it] = irGet(irContext.environment) } + arguments[0] = irGet(local) } } 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/KneeCodegen.kt b/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt index 6d87994..b4f8c8e 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 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..7d00cab 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,11 @@ 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 class KneeLogger( + private val kneeContext: KneeContext, private val collector: MessageCollector, private val verboseLogs: Boolean, private val verboseRuntime: Boolean @@ -35,15 +39,21 @@ 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(Name.identifier("println"), "kotlin", "io") + 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 +61,21 @@ 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(Name.identifier("println"), "kotlin", "io") + 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/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..a6d8974 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 @@ -37,9 +38,9 @@ object DownwardFunctionsIr { 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 = local.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }.indexOfFirst { it.name == param } 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) -> 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..21b374e 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,33 @@ object UpwardFunctionsIr { return irCall( symbols.functions(function).single() ).apply { - extensionReceiver = irGet(codecContext.environment) - putValueArgument(0, irGet(jmethodOwner)) - putValueArgument(1, irGet(jmethod)) - putValueArgument(2, irVararg( + symbol.owner.parameters + .indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } + .also { + arguments[it] = irGet(codecContext.environment) + } + arguments[0] = irGet(jmethodOwner) + arguments[1] = irGet(jmethod) + arguments[2] = 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,12 +102,16 @@ 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")) } } 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/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/utils/IrUtils.kt b/knee-compiler-plugin/src/main/kotlin/utils/IrUtils.kt index 1a4e60e..2769d58 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." } } @@ -173,13 +173,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 +191,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..c864b20 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,39 @@ 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 + 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 +209,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 +244,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() From 074dd65c36d5822fdad6caf3c80612e4eb4ef9d4 Mon Sep 17 00:00:00 2001 From: Sky233 Date: Sat, 25 Apr 2026 21:18:13 +0800 Subject: [PATCH 03/15] Update KneeComponentRegistrar and parameter mapping * Update KneeComponentRegistrar to include pluginId * Fix parameter mapping in UpwardFunctions to use resolved codec types * Cleanup unused imports in ImportInfo.kt --- .../src/main/kotlin/UpwardFunctions.kt | 14 +++++++++++++- .../src/main/kotlin/import/ImportInfo.kt | 13 +++++++------ .../main/kotlin/services/KneeComponentRegistrar.kt | 2 +- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt b/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt index d7be3e0..6dc9693 100644 --- a/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt +++ b/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt @@ -128,7 +128,19 @@ 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 -> + // Find the matching parameter and its resolved codec from the signature + val codec = signature.regularParameters.find { it.first == sourceParam }?.second + + // Copy the source parameter to the current function, replacing the type with the concrete type (localIrType) resolved by the codec + // If not found in the signature (e.g., DispatchReceiver), fall back to the original type + sourceParam.copyTo( + irFunction = this, + type = codec?.localIrType ?: sourceParam.type + ) + } // This function overrides the source function // Could also += source.overriddenSymbols, not sure if needed, we're not doing it elsewhere 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/services/KneeComponentRegistrar.kt b/knee-compiler-plugin/src/main/kotlin/services/KneeComponentRegistrar.kt index 61f7301..fa44ae5 100644 --- a/knee-compiler-plugin/src/main/kotlin/services/KneeComponentRegistrar.kt +++ b/knee-compiler-plugin/src/main/kotlin/services/KneeComponentRegistrar.kt @@ -10,7 +10,7 @@ import org.jetbrains.kotlin.config.CompilerConfiguration import java.io.File @OptIn(ExperimentalCompilerApi::class) -class KneeComponentRegistrar : CompilerPluginRegistrar() { +class KneeComponentRegistrar(override val pluginId: String) : CompilerPluginRegistrar() { override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { if (configuration[KneeCommandLineProcessor.KneeEnabled] == false) return val logs = configuration[CommonConfigurationKeys.MESSAGE_COLLECTOR_KEY]!! From 3f349b7245f7febc676d1fa73309933d60cdb206 Mon Sep 17 00:00:00 2001 From: Sky233 Date: Sat, 25 Apr 2026 22:38:31 +0800 Subject: [PATCH 04/15] Update compiler plugin and fix IR transformation * Set backing field visibility to private in IrUtils * Update KneeComponentRegistrar constructor and pluginId * Fix argument indexing in initKnee transformation * Add more descriptive error message for vararg element extraction --- knee-compiler-plugin/src/main/kotlin/Init.kt | 5 +++-- .../src/main/kotlin/services/KneeComponentRegistrar.kt | 4 +++- knee-compiler-plugin/src/main/kotlin/utils/IrUtils.kt | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/knee-compiler-plugin/src/main/kotlin/Init.kt b/knee-compiler-plugin/src/main/kotlin/Init.kt index 0decdb5..1be7d45 100644 --- a/knee-compiler-plugin/src/main/kotlin/Init.kt +++ b/knee-compiler-plugin/src/main/kotlin/Init.kt @@ -130,7 +130,7 @@ 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.arguments[0] + val dependencies = initializer.expression.arguments[1] initializer.expression.arguments[1] = builder.irVararg( elementType = context.symbols.moduleClass.defaultType, values = listOf( @@ -375,7 +375,8 @@ private fun KneeInitializer.collectDependencies(): List { */ private inline fun IrExpression?.varargElements(): List { if (this == null) return emptyList() - return (this as IrVararg).elements.map { + 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}") } } diff --git a/knee-compiler-plugin/src/main/kotlin/services/KneeComponentRegistrar.kt b/knee-compiler-plugin/src/main/kotlin/services/KneeComponentRegistrar.kt index fa44ae5..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(override val pluginId: String) : 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/utils/IrUtils.kt b/knee-compiler-plugin/src/main/kotlin/utils/IrUtils.kt index 2769d58..d1e396e 100644 --- a/knee-compiler-plugin/src/main/kotlin/utils/IrUtils.kt +++ b/knee-compiler-plugin/src/main/kotlin/utils/IrUtils.kt @@ -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 { From cfff543a41a6760cee083306fa906cb6764ce475 Mon Sep 17 00:00:00 2001 From: Sky233 Date: Sat, 25 Apr 2026 22:40:54 +0800 Subject: [PATCH 05/15] Version 1.3.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 75f6d88bf80fb924c93947369032ee8c9c6ef7d0 Mon Sep 17 00:00:00 2001 From: Sky233 Date: Sun, 26 Apr 2026 01:12:40 +0800 Subject: [PATCH 06/15] Update IR hierarchy traversal to support IrClass parents * Handle IrClass in addition to IrFile when determining package name * Improve error handling for unexpected declaration parents --- .../src/main/kotlin/codegen/KneeCodegen.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt b/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt index b4f8c8e..dae3926 100644 --- a/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt +++ b/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt @@ -10,6 +10,7 @@ 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.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.impl.IrClassImpl import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.FqName import java.io.File @@ -52,7 +53,12 @@ class KneeCodegen(private val context: KneeContext, val root: File, val verbose: // 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() From d954672f35f0abdb2bf2e31e9b5c1c1d949f59c5 Mon Sep 17 00:00:00 2001 From: Sky233 Date: Sun, 26 Apr 2026 02:15:56 +0800 Subject: [PATCH 07/15] Remove JvmStatic from upward functions * Remove `@JvmStatic` annotation from JNI private functions as they are not necessarily members of named or companion objects * Minor whitespace cleanup --- knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt b/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt index 6dc9693..05cf436 100644 --- a/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt +++ b/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt @@ -55,7 +55,8 @@ private fun KneeUpwardFunction.makeCodegen( val spec = FunSpec .builder(signature.jniInfo.name(includeAncestors = false).asString()) .addModifiers(KModifier.PRIVATE) - .addAnnotation(ClassName.bestGuess("kotlin.jvm.JvmStatic")) + // 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) // Parameters @@ -129,7 +130,7 @@ private fun KneeUpwardFunction.makeIr(context: KneeContext, signature: UpwardFun // 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()) - + parameters = source.parameters.map { sourceParam -> // Find the matching parameter and its resolved codec from the signature val codec = signature.regularParameters.find { it.first == sourceParam }?.second From a972015ce757062c78ea005c35c1275b17c0426d Mon Sep 17 00:00:00 2001 From: Sky233 Date: Sun, 26 Apr 2026 02:22:57 +0800 Subject: [PATCH 08/15] Include type arguments in disambiguation name * Support type arguments in `ParameterizedTypeName.disambiguationName` * Update `CodegenFunction` UID logic --- .../src/main/kotlin/codegen/CodegenDeclaration.kt | 6 ++++-- knee-compiler-plugin/src/main/kotlin/utils/PoetUtils.kt | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) 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/utils/PoetUtils.kt b/knee-compiler-plugin/src/main/kotlin/utils/PoetUtils.kt index c864b20..7ea6a3f 100644 --- a/knee-compiler-plugin/src/main/kotlin/utils/PoetUtils.kt +++ b/knee-compiler-plugin/src/main/kotlin/utils/PoetUtils.kt @@ -174,7 +174,12 @@ val TypeName.disambiguationName: String get() { return when (this) { is ClassName -> canonicalName - is ParameterizedTypeName -> rawType.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}") } From 64a823b0e46443548e19286aaf31805f9f63934a Mon Sep 17 00:00:00 2001 From: Sky233 Date: Sun, 26 Apr 2026 02:44:24 +0800 Subject: [PATCH 09/15] Add `containerForDeclaration` to `KneeCodegen` (#17) * Implement `containerForDeclaration` to build a `CodegenDeclaration` hierarchy * Refactor `KneeInterface` to use the new container resolution logic * Support companion object and property accessor detection during container generation --- .../src/main/kotlin/Interfaces.kt | 164 +++++++++++++----- .../src/main/kotlin/codegen/KneeCodegen.kt | 35 +++- 2 files changed, 154 insertions(+), 45 deletions(-) diff --git a/knee-compiler-plugin/src/main/kotlin/Interfaces.kt b/knee-compiler-plugin/src/main/kotlin/Interfaces.kt index bf2c386..cb8303d 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() - arguments[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[0] = irString( + signature.jniInfo.name(false).asString() + "::" + signature.jniInfo.signature + ) + } + } } if (!context.useExport2) { @@ -102,7 +134,12 @@ 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 = + runCatching { + codegen.containerForDeclaration(source, importInfo) + }.recover { + codegen.prepareContainer(source, importInfo) + }.getOrThrow() val builder = when { source.isFun -> TypeSpec.funInterfaceBuilder(source.name.asString()) else -> TypeSpec.interfaceBuilder(source.name.asString()) @@ -123,7 +160,12 @@ 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 = + runCatching { + codegen.containerForDeclaration(source, importInfo) + }.recover { + codegen.prepareContainer(source, importInfo) + }.getOrThrow() val builder = TypeSpec.classBuilder(name).apply { when { exported1 -> addModifiers(KModifier.PUBLIC) @@ -156,7 +198,8 @@ 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.superTypes = + listOf(sourceConcreteType, superClass.typeWith(sourceConcreteType)) wrapperClass.createThisReceiverParameter() // receiver } container.addChild(wrapperClass) @@ -187,11 +230,14 @@ private fun KneeInterface.makeIrImplementationContents(context: KneeContext) { // context.log.injectLog(this, CodegenType.from(irImplementation.defaultType).jvmClassName) +irDelegatingConstructorCall(superConstructor).apply { - 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]) + 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 arguments[2] = irString(CodegenType.from(sourceConcreteType).jvmClassName) - arguments[3] = irString(CodegenType.from(irImplementation.defaultType).jvmClassName) + arguments[3] = + irString(CodegenType.from(irImplementation.defaultType).jvmClassName) // Name and signature of the companion object function, alternated val allExportedFunctions = upwardFunctions + @@ -200,7 +246,12 @@ private fun KneeInterface.makeIrImplementationContents(context: KneeContext) { 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) @@ -209,7 +260,12 @@ private fun KneeInterface.makeIrImplementationContents(context: KneeContext) { ) } 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 + ) } } } @@ -258,7 +314,10 @@ 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 { typeArguments[0] = localIrType arguments[0] = irGet(irContext.environment) @@ -272,7 +331,10 @@ 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 { @@ -285,13 +347,20 @@ class InterfaceCodec( 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 { - arguments[0] = irGet(irContext.environment) // environment - arguments[1] = irGet(jni) // jobject - }) + +irReturn( + irCallConstructor( + interfaceImplClass.primaryConstructor!!.symbol, + emptyList() + ).apply { + arguments[0] = irGet(irContext.environment) // environment + arguments[1] = irGet(jni) // jobject + }) } ) } @@ -303,7 +372,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 @@ -318,13 +390,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/codegen/KneeCodegen.kt b/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt index dae3926..10bbf85 100644 --- a/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt +++ b/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt @@ -39,6 +39,37 @@ class KneeCodegen(private val context: KneeContext, val root: File, val verbose: } } + fun containerForDeclaration( + declaration: IrDeclaration, + importInfo: ImportInfo?, + detectPropertyAccessors: Boolean = true, + createCompanionObject: Boolean = false, + ): CodegenDeclaration<*> { + 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?, @@ -59,7 +90,7 @@ class KneeCodegen(private val context: KneeContext, val root: File, val verbose: 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})" } @@ -79,7 +110,7 @@ class KneeCodegen(private val context: KneeContext, val root: File, val verbose: } return candidate } - + /* fun containerOf( declaration: IrDeclaration, importInfo: ImportInfo?, From 69423d2257066d6ccc1531fef9544813debcbf95 Mon Sep 17 00:00:00 2001 From: Sky233 Date: Sun, 26 Apr 2026 18:29:57 +0800 Subject: [PATCH 10/15] Refactor container resolution and update symbol finding in Knee compiler plugin * Introduce `ensureContainer` in `KneeCodegen` to safely resolve or prepare declaration containers. * Update `KneeLogger` to use `CallableId` and `FqName` for finding `println` functions, improving compatibility with IR symbol finder APIs. * Refactor parameter mapping in `UpwardFunctions` to use name-based lookup and improve type substitution logic. * Enhance `KneeCodegen` IR preparation to handle constructor logic in file-level containers and improve code formatting. * Standardize container acquisition across classes, interfaces, objects, properties, and functions using the new `ensureContainer` helper. --- .../src/main/kotlin/Classes.kt | 2 +- .../src/main/kotlin/DownwardFunctions.kt | 6 +-- .../src/main/kotlin/DownwardProperties.kt | 6 +-- knee-compiler-plugin/src/main/kotlin/Enums.kt | 2 +- knee-compiler-plugin/src/main/kotlin/Init.kt | 2 +- .../src/main/kotlin/Interfaces.kt | 14 +----- .../src/main/kotlin/Objects.kt | 2 +- .../src/main/kotlin/UpwardFunctions.kt | 13 +++-- .../src/main/kotlin/codegen/KneeCodegen.kt | 47 ++++++++++++++++--- .../src/main/kotlin/context/KneeLogger.kt | 18 ++++++- 10 files changed, 74 insertions(+), 38 deletions(-) diff --git a/knee-compiler-plugin/src/main/kotlin/Classes.kt b/knee-compiler-plugin/src/main/kotlin/Classes.kt index 78b66cf..67255c6 100644 --- a/knee-compiler-plugin/src/main/kotlin/Classes.kt +++ b/knee-compiler-plugin/src/main/kotlin/Classes.kt @@ -59,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()) diff --git a/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt b/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt index 0b4eeb2..d42b3c5 100644 --- a/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt +++ b/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt @@ -157,17 +157,17 @@ private fun KneeDownwardFunction.makeCodegen( // 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( + is Kind.ClassConstructor -> codegen.ensureContainer( source, kind.importInfo, createCompanionObject = true ) - else -> codegen.prepareContainer(source, kind.importInfo, detectPropertyAccessors = false) + else -> codegen.ensureContainer(source, kind.importInfo, detectPropertyAccessors = false) } localContainer.addChild(localFun) 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 ec58227..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) } diff --git a/knee-compiler-plugin/src/main/kotlin/Init.kt b/knee-compiler-plugin/src/main/kotlin/Init.kt index 1be7d45..162959f 100644 --- a/knee-compiler-plugin/src/main/kotlin/Init.kt +++ b/knee-compiler-plugin/src/main/kotlin/Init.kt @@ -387,7 +387,7 @@ private fun KneeCodegen.makeCodegenModule( exportedTypes: List ) { val name = module.source.name.asString() - val container = prepareContainer(module.source, null) + 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") diff --git a/knee-compiler-plugin/src/main/kotlin/Interfaces.kt b/knee-compiler-plugin/src/main/kotlin/Interfaces.kt index cb8303d..f9aeb9e 100644 --- a/knee-compiler-plugin/src/main/kotlin/Interfaces.kt +++ b/knee-compiler-plugin/src/main/kotlin/Interfaces.kt @@ -134,12 +134,7 @@ fun processInterface( * We use addChildIfNeeded for this. */ private fun KneeInterface.makeCodegenClone(codegen: KneeCodegen) { - val container = - runCatching { - codegen.containerForDeclaration(source, importInfo) - }.recover { - codegen.prepareContainer(source, importInfo) - }.getOrThrow() + val container = codegen.ensureContainer(source, importInfo) val builder = when { source.isFun -> TypeSpec.funInterfaceBuilder(source.name.asString()) else -> TypeSpec.interfaceBuilder(source.name.asString()) @@ -160,12 +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 = - runCatching { - codegen.containerForDeclaration(source, importInfo) - }.recover { - codegen.prepareContainer(source, importInfo) - }.getOrThrow() + val container = codegen.ensureContainer(source, importInfo) val builder = TypeSpec.classBuilder(name).apply { when { exported1 -> addModifiers(KModifier.PUBLIC) 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 05cf436..3d0cac8 100644 --- a/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt +++ b/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt @@ -21,6 +21,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.* @@ -95,7 +96,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 @@ -132,14 +133,12 @@ private fun KneeUpwardFunction.makeIr(context: KneeContext, signature: UpwardFun // copyValueParametersFrom(source, kind.importInfo?.substitutionMap ?: emptyMap()) parameters = source.parameters.map { sourceParam -> - // Find the matching parameter and its resolved codec from the signature - val codec = signature.regularParameters.find { it.first == sourceParam }?.second - - // Copy the source parameter to the current function, replacing the type with the concrete type (localIrType) resolved by the codec - // If not found in the signature (e.g., DispatchReceiver), fall back to the original type + val codec = signature.regularParameters.find { it.first == sourceParam.name }?.second sourceParam.copyTo( irFunction = this, - type = codec?.localIrType ?: sourceParam.type + type = codec?.localIrType + ?: kind.importInfo?.substitutor?.substitute(sourceParam.type.simple("makeIr")) as? IrSimpleType + ?: sourceParam.type ) } diff --git a/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt b/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt index 10bbf85..c391874 100644 --- a/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt +++ b/knee-compiler-plugin/src/main/kotlin/codegen/KneeCodegen.kt @@ -10,7 +10,6 @@ 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.ir.declarations.* -import org.jetbrains.kotlin.ir.declarations.impl.IrClassImpl import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.FqName import java.io.File @@ -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,6 +39,27 @@ class KneeCodegen(private val context: KneeContext, val root: File, val verbose: } } + 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?, @@ -76,10 +97,11 @@ class KneeCodegen(private val context: KneeContext, val root: File, val verbose: 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 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] @@ -156,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) { @@ -164,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) { @@ -186,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/KneeLogger.kt b/knee-compiler-plugin/src/main/kotlin/context/KneeLogger.kt index 7d00cab..f6927e1 100644 --- a/knee-compiler-plugin/src/main/kotlin/context/KneeLogger.kt +++ b/knee-compiler-plugin/src/main/kotlin/context/KneeLogger.kt @@ -15,6 +15,8 @@ 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, @@ -43,7 +45,13 @@ class KneeLogger( if (printlnIrString == null) { val builtIns = kneeContext.plugin.irBuiltIns val symbolFinder = builtIns.symbolFinder - val function = symbolFinder.findFunctions(Name.identifier("println"), "kotlin", "io") + + 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 } @@ -65,7 +73,13 @@ class KneeLogger( if (printlnIrAny == null) { val builtIns = kneeContext.plugin.irBuiltIns val symbolFinder = builtIns.symbolFinder - val function = symbolFinder.findFunctions(Name.identifier("println"), "kotlin", "io") + + 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 } From bfcbfe4d5d1e7ce3404bb408b9d2982cfbdb02ad Mon Sep 17 00:00:00 2001 From: Sky233 Date: Sun, 26 Apr 2026 18:31:41 +0800 Subject: [PATCH 11/15] Update Kotlin and Android Gradle Plugin versions in tests * Update Kotlin to 2.3.0 across test modules * Update Android Gradle Plugin to 8.13.0 across test modules * Add `@KneeClass` and `@Knee` annotations to `Outer` class and `Inner.value` property in `test-interfaces` definitions --- tests/test-classes/build.gradle.kts | 4 ++-- tests/test-coroutines/build.gradle.kts | 4 ++-- tests/test-imports/build.gradle.kts | 4 ++-- tests/test-interfaces/build.gradle.kts | 4 ++-- tests/test-interfaces/src/backendMain/kotlin/Definintions.kt | 2 ++ tests/test-misc/build.gradle.kts | 4 ++-- tests/test-primitives/build.gradle.kts | 4 ++-- 7 files changed, 14 insertions(+), 12 deletions(-) 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") } From 5a0f6379fadc2f2a1c4837ea2702a80e987840fd Mon Sep 17 00:00:00 2001 From: Sky233 Date: Sun, 26 Apr 2026 20:10:40 +0800 Subject: [PATCH 12/15] Add JvmStatic annotation to upward functions --- knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt b/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt index 3d0cac8..169e36f 100644 --- a/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt +++ b/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt @@ -57,7 +57,7 @@ private fun KneeUpwardFunction.makeCodegen( .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")) + .addAnnotation(ClassName.bestGuess("kotlin.jvm.JvmStatic")) .returns((if (signature.isSuspend) signature.suspendResult else signature.result).encodedType.jvmOrNull?.name ?: UNIT) // Parameters From f69e43a9069573b4118de35746e9de595a5a6e93 Mon Sep 17 00:00:00 2001 From: Sky233 Date: Sun, 26 Apr 2026 21:36:11 +0800 Subject: [PATCH 13/15] Fix imported Flow and StateFlow bridge generation - fix parameter mapping for imported suspend interface members - fix reverse FlowCollector.emit invocation on JVM - fix method(key) lookup in generated interface wrappers - improve JNI wrapper reference lifecycle for imported interfaces - add regression tests for direct StateFlow access --- .../src/main/kotlin/DownwardFunctions.kt | 5 + .../src/main/kotlin/Interfaces.kt | 4 +- .../src/main/kotlin/UpwardFunctions.kt | 45 +++--- .../kotlin/features/KneeDownwardFunction.kt | 5 + .../kotlin/functions/DownwardFunctionsIr.kt | 35 ++++- .../functions/ImportedInterfaceAdapters.kt | 135 ++++++++++++++++++ .../kotlin/functions/UpwardFunctionsIr.kt | 20 +-- .../src/main/kotlin/symbols/SymbolIds.kt | 6 +- .../kotlin/compiler/ImportedFlowCalls.kt | 32 +++++ .../kotlin/compiler/JvmInterfaceWrapper.kt | 23 ++- .../backendMain/kotlin/compiler/Suspend.kn.kt | 18 +-- 11 files changed, 272 insertions(+), 56 deletions(-) create mode 100644 knee-compiler-plugin/src/main/kotlin/functions/ImportedInterfaceAdapters.kt create mode 100644 knee-runtime/src/backendMain/kotlin/compiler/ImportedFlowCalls.kt diff --git a/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt b/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt index d42b3c5..4ccdd16 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 @@ -37,6 +38,7 @@ fun processDownwardFunction( 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) } @@ -275,6 +277,7 @@ private fun KneeDownwardFunction.makeIr( +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( @@ -292,6 +295,7 @@ private fun KneeDownwardFunction.makeIr( val raw = irInvoke( lambda.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }, source, + target, signature, codecContext ) @@ -373,6 +377,7 @@ private fun KneeDownwardFunction.makeIr( lambda.parameters .filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }, source, + importedAdapter ?: source, signature, codecContext ) diff --git a/knee-compiler-plugin/src/main/kotlin/Interfaces.kt b/knee-compiler-plugin/src/main/kotlin/Interfaces.kt index f9aeb9e..da6e3c2 100644 --- a/knee-compiler-plugin/src/main/kotlin/Interfaces.kt +++ b/knee-compiler-plugin/src/main/kotlin/Interfaces.kt @@ -114,7 +114,7 @@ fun processInterface( interface_.irGetMethod = { signature -> irCall(methodFromSignature).apply { dispatchReceiver = irThis() - arguments[0] = irString( + arguments[methodFromSignature.parameters.indexOfFirst { it.name.asString() == "key" }] = irString( signature.jniInfo.name(false).asString() + "::" + signature.jniInfo.signature ) } @@ -395,4 +395,4 @@ class InterfaceCodec( ) return "${local}_" } -} \ No newline at end of file +} diff --git a/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt b/knee-compiler-plugin/src/main/kotlin/UpwardFunctions.kt index 169e36f..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 @@ -149,6 +150,7 @@ 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)) @@ -157,13 +159,11 @@ private fun KneeUpwardFunction.makeIr(context: KneeContext, signature: UpwardFun +irReturn(irCall( callee = context.symbols.functions(useEnv).single() ).apply { - symbol.owner.parameters - .indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } - .also { - arguments[it] = kind.parent.irGetVirtualMachine(this@irBlockBody) - } + 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[0] = irLambda( + arguments[blockIndex] = irLambda( context = context, parent = parent, content = { lambda -> @@ -185,18 +185,31 @@ private fun KneeUpwardFunction.makeIr(context: KneeContext, signature: UpwardFun +irReturn(irCall(context.symbols.functions(kneeInvokeKnSuspend).single()).apply { typeArguments[0] = signature.result.encodedType.knOrNull ?: context.symbols.builtIns.unitType typeArguments[1] = signature.result.localIrType - arguments[0] = kind.parent.irGetVirtualMachine(this@irBlockBody) + arguments[0] = irGet(virtualMachine) arguments[1] = irLambda(context, parent) { lambda -> - val env = lambda.addValueParameter("_env", envType) 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)) - } + +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) @@ -214,4 +227,4 @@ private fun KneeUpwardFunction.makeIr(context: KneeContext, signature: UpwardFun }.also { irProducts.add(it) } -} \ No newline at end of file +} 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/functions/DownwardFunctionsIr.kt b/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionsIr.kt index a6d8974..d8cdf68 100644 --- a/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionsIr.kt +++ b/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionsIr.kt @@ -21,24 +21,51 @@ 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 targetRegularParameters = target.parameters + .filter { it.kind == IrParameterKind.Regular || it.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 { + 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 = targetRegularParameters + .indexOfFirst { it.name == name } + .takeIf { it >= 0 } + ?: 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.parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }.indexOfFirst { it.name == param } + val targetIndex = targetRegularParameters + .indexOfFirst { it.name == param } + .takeIf { it >= 0 } + ?: (targetRegularBaseIndex + index) + require(targetIndex in targetRegularParameters.indices) { + "Could not map parameter '$param' from ${local.fqNameWhenAvailable} to ${target.fqNameWhenAvailable}." + } codecContext.logger.injectLog(this@irInvoke, "$logPrefix Decoding parameter $param with $codec") arguments[targetIndex] = irDecode(codecContext, inputs[inputIndex]) } @@ -66,4 +93,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..de15026 --- /dev/null +++ b/knee-compiler-plugin/src/main/kotlin/functions/ImportedInterfaceAdapters.kt @@ -0,0 +1,135 @@ +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 } + + 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[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/UpwardFunctionsIr.kt b/knee-compiler-plugin/src/main/kotlin/functions/UpwardFunctionsIr.kt index 21b374e..bb75dde 100644 --- a/knee-compiler-plugin/src/main/kotlin/functions/UpwardFunctionsIr.kt +++ b/knee-compiler-plugin/src/main/kotlin/functions/UpwardFunctionsIr.kt @@ -64,14 +64,16 @@ object UpwardFunctionsIr { return irCall( symbols.functions(function).single() ).apply { - symbol.owner.parameters - .indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } - .also { - arguments[it] = irGet(codecContext.environment) - } - arguments[0] = irGet(jmethodOwner) - arguments[1] = irGet(jmethod) - arguments[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 ) @@ -115,4 +117,4 @@ object UpwardFunctionsIr { irDecode(codecContext, irTemporary(rawValue, "result")) } } -} \ No newline at end of file +} 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-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) } } } - From 070121e2e65510dcee25e438425bedcf322c38e9 Mon Sep 17 00:00:00 2001 From: Sky233 Date: Mon, 27 Apr 2026 11:05:55 +0800 Subject: [PATCH 14/15] Correct Knee class member argument binding - bind downward member-call arguments by actual IR parameter slots instead of filtered regular-parameter indexes - fix imported interface adapters to use the same slot mapping - prevent @KneeClass member calls like getString(key, default) from dropping the trailing argument at runtime - fix native exception rethrow bridging to pass both env and throwable - add regression coverage for class members with multiple parameters --- .../src/main/kotlin/DownwardFunctions.kt | 8 +++-- .../kotlin/functions/DownwardFunctionsIr.kt | 32 +++++++++++-------- .../functions/ImportedInterfaceAdapters.kt | 4 ++- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt b/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt index 4ccdd16..e6d70cc 100644 --- a/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt +++ b/knee-compiler-plugin/src/main/kotlin/DownwardFunctions.kt @@ -310,10 +310,12 @@ private fun KneeDownwardFunction.makeIr( context.symbols.functions(rethrowNativeException) .single() ).apply { - symbol.owner.parameters + val extensionIndex = symbol.owner.parameters .indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } - .also { arguments[it] = irGet(environment) } - arguments[0] = irGet(catch) + 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. diff --git a/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionsIr.kt b/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionsIr.kt index d8cdf68..47e9d05 100644 --- a/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionsIr.kt +++ b/knee-compiler-plugin/src/main/kotlin/functions/DownwardFunctionsIr.kt @@ -27,8 +27,8 @@ object DownwardFunctionsIr { ): IrExpression { val logPrefix = "FunctionsIr.irInvoke(${local.fqNameWhenAvailable})" codecContext.logger.injectLog(this, "$logPrefix START") - val targetRegularParameters = target.parameters - .filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + 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 @@ -36,6 +36,21 @@ object DownwardFunctionsIr { else -> 0 } + 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) -> @@ -47,10 +62,7 @@ object DownwardFunctionsIr { dispatchReceiver = decoded } else -> { - val targetIndex = targetRegularParameters - .indexOfFirst { it.name == name } - .takeIf { it >= 0 } - ?: 0 + val targetIndex = targetArgumentIndex(name, 0) arguments[targetIndex] = decoded } } @@ -59,13 +71,7 @@ object DownwardFunctionsIr { with(codec) { // note: targetIndex != index because of copy parameters! val inputIndex = index + signature.knPrefixParameters.size + signature.extraParameters.size - val targetIndex = targetRegularParameters - .indexOfFirst { it.name == param } - .takeIf { it >= 0 } - ?: (targetRegularBaseIndex + index) - require(targetIndex in targetRegularParameters.indices) { - "Could not map parameter '$param' from ${local.fqNameWhenAvailable} to ${target.fqNameWhenAvailable}." - } + val targetIndex = targetArgumentIndex(param, targetRegularBaseIndex + index) codecContext.logger.injectLog(this@irInvoke, "$logPrefix Decoding parameter $param with $codec") arguments[targetIndex] = irDecode(codecContext, inputs[inputIndex]) } diff --git a/knee-compiler-plugin/src/main/kotlin/functions/ImportedInterfaceAdapters.kt b/knee-compiler-plugin/src/main/kotlin/functions/ImportedInterfaceAdapters.kt index de15026..5e50b13 100644 --- a/knee-compiler-plugin/src/main/kotlin/functions/ImportedInterfaceAdapters.kt +++ b/knee-compiler-plugin/src/main/kotlin/functions/ImportedInterfaceAdapters.kt @@ -46,6 +46,8 @@ internal fun KneeDownwardFunction.ensureImportedAdapter( .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 @@ -88,7 +90,7 @@ internal fun KneeDownwardFunction.ensureImportedAdapter( } else { call.dispatchReceiver = irGet(receiver) targetRegularParams.forEachIndexed { index, _ -> - call.arguments[index] = irGet(params[index]) + call.arguments[targetRegularParamSlots[index].index] = irGet(params[index]) } } +irReturn(call) From ea1b7327ac22398631b69a238cb5eca424814f43 Mon Sep 17 00:00:00 2001 From: Sky233 Date: Mon, 27 Apr 2026 23:04:17 +0800 Subject: [PATCH 15/15] Bind collection codec IR calls by actual parameter slots Fix generated IR for collection helpers such as encodeArray, decodeIntoArray, encodeList and decodeIntoList by assigning the JniEnvironment extension receiver and collection argument to their actual parameter slots. This prevents Kotlin/Native IrLinkageError crashes where encodeArray or decodeIntoArray is called without the array parameter. --- .../src/main/kotlin/codec/CollectionCodec.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/knee-compiler-plugin/src/main/kotlin/codec/CollectionCodec.kt b/knee-compiler-plugin/src/main/kotlin/codec/CollectionCodec.kt index 1433ea1..3adc7de 100644 --- a/knee-compiler-plugin/src/main/kotlin/codec/CollectionCodec.kt +++ b/knee-compiler-plugin/src/main/kotlin/codec/CollectionCodec.kt @@ -167,10 +167,12 @@ class CollectionCodec( val decode = runtimeHelperClass.functions.single { it.name.asString() == "decodeInto${collectionKind.name}" } return irCall(decode).apply { dispatchReceiver = irGet(codec) - symbol.owner.parameters + val extensionIndex = symbol.owner.parameters .indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } - .also { arguments[it] = irGet(irContext.environment) } - arguments[0] = irGet(jni) + val arrayIndex = symbol.owner.parameters + .indexOfFirst { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + arguments[extensionIndex] = irGet(irContext.environment) + arguments[arrayIndex] = irGet(jni) } } @@ -180,10 +182,12 @@ class CollectionCodec( val encode = runtimeHelperClass.functions.single { it.name.asString() == "encode${collectionKind.name}" } return irCall(encode).apply { dispatchReceiver = irGet(codec) - symbol.owner.parameters + val extensionIndex = symbol.owner.parameters .indexOfFirst { it.kind == IrParameterKind.ExtensionReceiver } - .also { arguments[it] = irGet(irContext.environment) } - arguments[0] = irGet(local) + val collectionIndex = symbol.owner.parameters + .indexOfFirst { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context } + arguments[extensionIndex] = irGet(irContext.environment) + arguments[collectionIndex] = irGet(local) } } @@ -251,4 +255,4 @@ class CollectionCodec( } } } -} \ No newline at end of file +}