Skip to content

Commit b47f431

Browse files
shishkin-pavelSpace Cloud
authored and
Space Cloud
committed
compose-compiler: stableprop generation
generate stableprop and getter function exported to metadata so it is visible from outside of the module and can be accessed witout hacks
1 parent 357fcfc commit b47f431

File tree

6 files changed

+176
-53
lines changed

6 files changed

+176
-53
lines changed

plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt

+7-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
4343
import org.jetbrains.kotlin.backend.common.serialization.DeclarationTable
4444
import org.jetbrains.kotlin.backend.common.serialization.signature.IdSignatureFactory
4545
import org.jetbrains.kotlin.backend.common.serialization.signature.PublicIdSignatureComputer
46+
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
47+
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
48+
import org.jetbrains.kotlin.descriptors.ClassDescriptor
4649
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsGlobalDeclarationTable
4750
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerIr
4851
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
@@ -51,6 +54,7 @@ import org.jetbrains.kotlin.platform.isJs
5154
import org.jetbrains.kotlin.platform.isWasm
5255
import org.jetbrains.kotlin.platform.jvm.isJvm
5356
import org.jetbrains.kotlin.platform.konan.isNative
57+
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
5458

5559
class ComposeIrGenerationExtension(
5660
@Suppress("unused") private val liveLiteralsEnabled: Boolean = false,
@@ -69,6 +73,7 @@ class ComposeIrGenerationExtension(
6973
private val stableTypeMatchers: Set<FqNameMatcher> = emptySet(),
7074
private val moduleMetricsFactory: ((StabilityInferencer) -> ModuleMetrics)? = null,
7175
private val descriptorSerializerContext: ComposeDescriptorSerializerContext? = null,
76+
private val messageCollector: MessageCollector? = null
7277
) : IrGenerationExtension {
7378
var metrics: ModuleMetrics = EmptyModuleMetrics
7479
private set
@@ -124,7 +129,8 @@ class ComposeIrGenerationExtension(
124129
classStabilityInferredCollection = descriptorSerializerContext
125130
?.classStabilityInferredCollection?.takeIf {
126131
!pluginContext.platform.isJvm()
127-
}
132+
},
133+
messageCollector
128134
).lower(moduleFragment)
129135

130136
LiveLiteralTransformer(

plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt

+1
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ class ComposePluginRegistrar : org.jetbrains.kotlin.compiler.plugin.ComponentReg
479479
stableTypeMatchers = stableTypeMatchers,
480480
moduleMetricsFactory = moduleMetricsFactory,
481481
descriptorSerializerContext = descriptorSerializerContext,
482+
messageCollector = msgCollector
482483
)
483484
}
484485
}

plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/KtxNameConventions.kt

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ object KtxNameConventions {
99
val FORCE_PARAMETER = Name.identifier("\$force")
1010
val STABILITY_FLAG = Name.identifier("\$stable")
1111
val STABILITY_PROP_FLAG = Name.identifier("\$stableprop")
12+
val STABILITY_GETTER_FLAG = Name.identifier("\$stableprop_getter")
1213
val DEFAULT_PARAMETER = Name.identifier("\$default")
1314
val JOINKEY = Name.identifier("joinKey")
1415
val STARTRESTARTGROUP = Name.identifier("startRestartGroup")

plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt

+139-37
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import androidx.compose.compiler.plugins.kotlin.analysis.knownUnstable
3434
import androidx.compose.compiler.plugins.kotlin.irTrace
3535
import com.intellij.openapi.progress.ProcessCanceledException
3636
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
37+
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
3738
import org.jetbrains.kotlin.backend.jvm.ir.isInlineClassType
3839
import org.jetbrains.kotlin.builtins.PrimitiveType
3940
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
@@ -45,10 +46,11 @@ import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
4546
import org.jetbrains.kotlin.ir.builders.declarations.buildField
4647
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
4748
import org.jetbrains.kotlin.ir.builders.declarations.buildProperty
49+
import org.jetbrains.kotlin.ir.builders.irBlockBody
50+
import org.jetbrains.kotlin.ir.builders.irReturn
4851
import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer
4952
import org.jetbrains.kotlin.ir.declarations.IrAttributeContainer
5053
import org.jetbrains.kotlin.ir.declarations.IrClass
51-
import org.jetbrains.kotlin.ir.declarations.IrDeclarationContainer
5254
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
5355
import org.jetbrains.kotlin.ir.declarations.IrField
5456
import org.jetbrains.kotlin.ir.declarations.IrFile
@@ -86,6 +88,7 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrBranchImpl
8688
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
8789
import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl
8890
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
91+
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
8992
import org.jetbrains.kotlin.ir.expressions.impl.IrElseBranchImpl
9093
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl
9194
import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl
@@ -128,6 +131,7 @@ import org.jetbrains.kotlin.ir.types.typeWith
128131
import org.jetbrains.kotlin.ir.util.DeepCopySymbolRemapper
129132
import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET
130133
import org.jetbrains.kotlin.ir.util.addChild
134+
import org.jetbrains.kotlin.ir.util.constructors
131135
import org.jetbrains.kotlin.ir.util.defaultType
132136
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
133137
import org.jetbrains.kotlin.ir.util.functions
@@ -155,11 +159,23 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
155159
import org.jetbrains.kotlin.util.OperatorNameConventions
156160
import org.jetbrains.kotlin.utils.DFS
157161

162+
import androidx.compose.compiler.plugins.kotlin.lower.hiddenfromobjc.hiddenFromObjCClassId
163+
import org.jetbrains.kotlin.GeneratedDeclarationKey
164+
import org.jetbrains.kotlin.descriptors.ClassDescriptor
165+
import org.jetbrains.kotlin.fir.declarations.utils.klibSourceFile
166+
import org.jetbrains.kotlin.fir.lazy.Fir2IrLazyClass
167+
import org.jetbrains.kotlin.ir.declarations.IrPackageFragment
168+
import org.jetbrains.kotlin.ir.util.packageFqName
169+
import org.jetbrains.kotlin.library.metadata.DeserializedSourceFile
170+
171+
object ComposeCompilerKey : GeneratedDeclarationKey()
172+
158173
abstract class AbstractComposeLowering(
159174
val context: IrPluginContext,
160175
val symbolRemapper: DeepCopySymbolRemapper,
161176
val metrics: ModuleMetrics,
162-
val stabilityInferencer: StabilityInferencer
177+
val stabilityInferencer: StabilityInferencer,
178+
protected val unstableClassesWarning: MutableSet<ClassDescriptor>? = null
163179
) : IrElementTransformerVoid(), ModuleLoweringPass {
164180
protected val builtIns = context.irBuiltIns
165181

@@ -379,15 +395,7 @@ abstract class AbstractComposeLowering(
379395
null
380396

381397
is Stability.Parameter -> resolve(parameter)
382-
is Stability.Runtime -> {
383-
val stableField = declaration.makeStabilityField()
384-
IrGetFieldImpl(
385-
UNDEFINED_OFFSET,
386-
UNDEFINED_OFFSET,
387-
stableField.symbol,
388-
stableField.type
389-
)
390-
}
398+
is Stability.Runtime -> declaration.getRuntimeStabilityValue()
391399

392400
is Stability.Unknown -> null
393401
}
@@ -778,6 +786,15 @@ abstract class AbstractComposeLowering(
778786
return irGet(variable.type, variable.symbol)
779787
}
780788

789+
protected fun irGetField(field: IrField): IrGetField {
790+
return IrGetFieldImpl(
791+
UNDEFINED_OFFSET,
792+
UNDEFINED_OFFSET,
793+
field.symbol,
794+
field.type
795+
)
796+
}
797+
781798
protected fun irIf(condition: IrExpression, body: IrExpression): IrExpression {
782799
return IrIfThenElseImpl(
783800
UNDEFINED_OFFSET,
@@ -898,61 +915,146 @@ abstract class AbstractComposeLowering(
898915
kotlinFqName.asString().replace(".", "_") + KtxNameConventions.STABILITY_PROP_FLAG
899916
)
900917

901-
fun IrClass.makeStabilityField(): IrField {
918+
private fun IrClass.uniqueStabilityGetterName(): Name = Name.identifier(
919+
kotlinFqName.asString().replace(".", "_") + KtxNameConventions.STABILITY_GETTER_FLAG
920+
)
921+
922+
private fun IrClass.getMetadataStabilityGetterFun(): IrSimpleFunctionSymbol? {
923+
val suitableFunctions = context.referenceFunctions(CallableId(this.packageFqName!!, uniqueStabilityGetterName()))
924+
return suitableFunctions.singleOrNull()
925+
}
926+
927+
private fun IrClass.getRuntimeStabilityValue(): IrExpression {
928+
if (context.platform.isJvm()) {
929+
val stableField = this.makeStabilityFieldJvm()
930+
return irGetField(stableField)
931+
} else {
932+
// since k2.0.10 compiler plugin adds special getter function that should be visible in metadata declarations
933+
val stabilityGetter = getMetadataStabilityGetterFun()
934+
if (stabilityGetter != null) {
935+
return irCall(stabilityGetter)
936+
}
937+
938+
// in case we have not found getter function and dependency was compiled with k1.9, we can rely on
939+
// IrGetField over property backing field because it was generated with `isConst = true` and should be initialized
940+
val classKotlinVersion =
941+
((this as? Fir2IrLazyClass)?.fir?.klibSourceFile as? DeserializedSourceFile)?.library?.versions?.compilerVersion
942+
if (classKotlinVersion != null && classKotlinVersion.startsWith("1.9")) {
943+
val stableField = this.buildStabilityProp(false)
944+
val backingField = stableField.backingField!!
945+
946+
return irGetField(backingField)
947+
}
948+
949+
// if we can not find stability getter function in metadata, dependency was compiled with older version of compiler plugin,
950+
// so we can not trust value produced from `irGetField` because it may contain uninitialized data on native targets
951+
// (there is no guarantees that any of static/toplevel functions were called at this point,
952+
// so no guarantees that package initializer was called and field value was initialized)
953+
// we treat those classes as `Unstable` and produce compilation warning that user may observe additional recompositions
954+
// and may need to update dependencies to version compiled with newer compiler plugin to get rid of them
955+
unstableClassesWarning?.add(this.descriptor)
956+
return irConst(StabilityBits.UNSTABLE.bitsForSlot(0))
957+
}
958+
}
959+
960+
internal fun IrClass.makeStabilityField(): IrField {
902961
return if (context.platform.isJvm()) {
903-
this.makeStabilityFieldJvm()
962+
makeStabilityFieldJvm()
904963
} else {
905-
this.makeStabilityFieldNonJvm()
964+
makeStabilityFieldNonJvm()
906965
}
907966
}
908967

909968
private fun IrClass.makeStabilityFieldJvm(): IrField {
910-
return context.irFactory.buildField {
911-
startOffset = SYNTHETIC_OFFSET
912-
endOffset = SYNTHETIC_OFFSET
913-
name = KtxNameConventions.STABILITY_FLAG
914-
isStatic = true
915-
isFinal = true
916-
type = context.irBuiltIns.intType
917-
visibility = DescriptorVisibilities.PUBLIC
918-
}.also { stabilityField ->
969+
return buildStabilityField(KtxNameConventions.STABILITY_FLAG).also { stabilityField ->
919970
stabilityField.parent = this@makeStabilityFieldJvm
971+
declarations += stabilityField
920972
}
921973
}
922974

923975
private fun IrClass.makeStabilityFieldNonJvm(): IrField {
924-
val stabilityFieldName = this.uniqueStabilityFieldName()
925-
val fieldParent = this.getPackageFragment()
976+
val prop = this.buildStabilityProp(true)
977+
return prop.backingField!!
978+
}
926979

980+
private fun buildStabilityField(fieldName: Name): IrField {
927981
return context.irFactory.buildField {
928982
startOffset = SYNTHETIC_OFFSET
929983
endOffset = SYNTHETIC_OFFSET
930-
name = stabilityFieldName
984+
name = fieldName
931985
isStatic = true
932986
isFinal = true
933987
type = context.irBuiltIns.intType
934988
visibility = DescriptorVisibilities.PUBLIC
935-
}.also { stabilityField ->
936-
stabilityField.parent = fieldParent
937-
makeStabilityProp(stabilityField, fieldParent)
938989
}
939990
}
940991

941-
private fun IrClass.makeStabilityProp(
942-
stabilityField: IrField,
943-
fieldParent: IrDeclarationContainer
944-
): IrProperty {
945-
return context.irFactory.buildProperty {
992+
private fun IrClass.buildStabilityProp(buildGetter: Boolean): IrProperty {
993+
val parent = this.getPackageFragment()
994+
995+
val propName = this.uniqueStabilityPropertyName()
996+
val existingProp = parent.declarations.firstOrNull {
997+
it is IrProperty && it.name == propName
998+
} as? IrProperty
999+
if (existingProp != null) {
1000+
return existingProp
1001+
}
1002+
1003+
val stabilityField = buildStabilityField(uniqueStabilityFieldName()).also {
1004+
it.parent = parent
1005+
}
1006+
1007+
val property = context.irFactory.buildProperty {
9461008
startOffset = SYNTHETIC_OFFSET
9471009
endOffset = SYNTHETIC_OFFSET
948-
name = this@makeStabilityProp.uniqueStabilityPropertyName()
1010+
name = propName
9491011
visibility = DescriptorVisibilities.PUBLIC
9501012
}.also { property ->
951-
property.parent = fieldParent
1013+
property.parent = parent
9521014
stabilityField.correspondingPropertySymbol = property.symbol
9531015
property.backingField = stabilityField
954-
fieldParent.addChild(property)
1016+
parent.addChild(property)
9551017
}
1018+
1019+
if (buildGetter) {
1020+
this.buildStabilityGetter(property, parent)
1021+
}
1022+
1023+
return property
1024+
}
1025+
1026+
private val hiddenFromObjCAnnotationSymbol: IrClassSymbol = getTopLevelClass(hiddenFromObjCClassId)
1027+
private val hiddenFromObjCAnnotation = IrConstructorCallImpl.fromSymbolOwner(
1028+
type = hiddenFromObjCAnnotationSymbol.defaultType,
1029+
constructorSymbol = hiddenFromObjCAnnotationSymbol.constructors.first()
1030+
)
1031+
1032+
private fun IrClass.buildStabilityGetter(stabilityProp: IrProperty, parent: IrPackageFragment) {
1033+
val getterName = uniqueStabilityGetterName()
1034+
1035+
val stabilityField = stabilityProp.backingField!!
1036+
1037+
// we could have created getter instead of separate function,
1038+
// but `registerFunctionAsMetadataVisible` is not working for field getter for some reason
1039+
// and there is no api to register properties as metadata-visible
1040+
val stabilityGetter = context.irFactory.buildFun {
1041+
startOffset = SYNTHETIC_OFFSET
1042+
endOffset = SYNTHETIC_OFFSET
1043+
name = getterName
1044+
returnType = stabilityField.type
1045+
visibility = DescriptorVisibilities.PUBLIC
1046+
origin = IrDeclarationOrigin.GeneratedByPlugin(ComposeCompilerKey)
1047+
annotations = listOf(hiddenFromObjCAnnotation)
1048+
}.also { fn ->
1049+
fn.parent = parent
1050+
fn.body = DeclarationIrBuilder(context, fn.symbol).irBlockBody {
1051+
+irReturn(irGetField(stabilityField))
1052+
}
1053+
parent.addChild(fn)
1054+
}
1055+
1056+
context.metadataDeclarationRegistrar.addMetadataVisibleAnnotationsToElement(stabilityGetter, hiddenFromObjCAnnotation)
1057+
context.metadataDeclarationRegistrar.registerFunctionAsMetadataVisible(stabilityGetter)
9561058
}
9571059

9581060
fun IrExpression.isStatic(): Boolean {

plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ClassStabilityTransformer.kt

+25-14
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import androidx.compose.compiler.plugins.kotlin.analysis.normalize
2727
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
2828
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
2929
import org.jetbrains.kotlin.backend.jvm.ir.isInlineClassType
30+
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
31+
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
32+
import org.jetbrains.kotlin.descriptors.ClassDescriptor
3033
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
3134
import org.jetbrains.kotlin.ir.IrImplementationDetail
3235
import org.jetbrains.kotlin.ir.IrStatement
@@ -36,7 +39,6 @@ import org.jetbrains.kotlin.ir.declarations.IrFile
3639
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
3740
import org.jetbrains.kotlin.ir.expressions.IrExpression
3841
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
39-
import org.jetbrains.kotlin.ir.expressions.impl.IrExpressionBodyImpl
4042
import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI
4143
import org.jetbrains.kotlin.ir.types.defaultType
4244
import org.jetbrains.kotlin.ir.util.DeepCopySymbolRemapper
@@ -50,6 +52,7 @@ import org.jetbrains.kotlin.ir.util.isFileClass
5052
import org.jetbrains.kotlin.ir.util.isInterface
5153
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
5254
import org.jetbrains.kotlin.platform.jvm.isJvm
55+
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
5356

5457
enum class StabilityBits(val bits: Int) {
5558
UNSTABLE(0b100),
@@ -67,8 +70,9 @@ class ClassStabilityTransformer(
6770
symbolRemapper: DeepCopySymbolRemapper,
6871
metrics: ModuleMetrics,
6972
stabilityInferencer: StabilityInferencer,
70-
private val classStabilityInferredCollection: ClassStabilityInferredCollection? = null
71-
) : AbstractComposeLowering(context, symbolRemapper, metrics, stabilityInferencer),
73+
private val classStabilityInferredCollection: ClassStabilityInferredCollection? = null,
74+
private val messageCollector: MessageCollector? = null
75+
) : AbstractComposeLowering(context, symbolRemapper, metrics, stabilityInferencer, mutableSetOf<ClassDescriptor>()),
7276
ClassLoweringPass,
7377
ModuleLoweringPass {
7478

@@ -78,6 +82,18 @@ class ClassStabilityTransformer(
7882

7983
override fun lower(module: IrModuleFragment) {
8084
module.transformChildrenVoid(this)
85+
86+
if (!context.platform.isJvm() && !unstableClassesWarning.isNullOrEmpty()) {
87+
val classIds = unstableClassesWarning.mapTo(mutableSetOf()) { it.fqNameSafe.toString() }
88+
val classesConcatenated = classIds.sorted().joinToString("\n")
89+
messageCollector?.report(
90+
CompilerMessageSeverity.WARNING,
91+
"Due to some of dependencies were built using older compiler plugin, stability of following classes " +
92+
"is considered `Unstable`, which may cause additional recompositions happening at runtime on non-JVM targets. " +
93+
"To prevent that consider updating dependency libraries to a newer version built with newer compose compiler plugin.\n" +
94+
classesConcatenated
95+
)
96+
}
8197
}
8298

8399
override fun lower(irClass: IrClass) {
@@ -197,16 +213,11 @@ class ClassStabilityTransformer(
197213

198214
@OptIn(IrImplementationDetail::class, UnsafeDuringIrConstructionAPI::class)
199215
private fun IrClass.addStabilityMarkerField(stabilityExpression: IrExpression) {
200-
val stabilityField = this.makeStabilityField().apply {
201-
initializer = context.irFactory.createExpressionBody(
202-
UNDEFINED_OFFSET,
203-
UNDEFINED_OFFSET,
204-
stabilityExpression
205-
)
206-
}
207-
208-
if (context.platform.isJvm()) {
209-
declarations += stabilityField
210-
}
216+
val stabilityField = makeStabilityField()
217+
stabilityField.initializer = context.irFactory.createExpressionBody(
218+
UNDEFINED_OFFSET,
219+
UNDEFINED_OFFSET,
220+
stabilityExpression
221+
)
211222
}
212223
}

0 commit comments

Comments
 (0)