blob: 8d6397eaa685705748c44b13b25b83049fedfc97 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.compiler.plugins.kotlin.lower
import androidx.compose.compiler.plugins.kotlin.ComposeClassIds
import androidx.compose.compiler.plugins.kotlin.ModuleMetrics
import androidx.compose.compiler.plugins.kotlin.analysis.Stability
import androidx.compose.compiler.plugins.kotlin.analysis.StabilityInferencer
import androidx.compose.compiler.plugins.kotlin.analysis.forEach
import androidx.compose.compiler.plugins.kotlin.analysis.hasStableMarker
import androidx.compose.compiler.plugins.kotlin.analysis.knownStable
import androidx.compose.compiler.plugins.kotlin.analysis.normalize
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.jvm.ir.isInlineClassType
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrExpressionBodyImpl
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.util.DeepCopySymbolRemapper
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.isAnnotationClass
import org.jetbrains.kotlin.ir.util.isAnonymousObject
import org.jetbrains.kotlin.ir.util.isEnumClass
import org.jetbrains.kotlin.ir.util.isEnumEntry
import org.jetbrains.kotlin.ir.util.isFileClass
import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.platform.jvm.isJvm
enum class StabilityBits(val bits: Int) {
UNSTABLE(0b100),
STABLE(0b000);
fun bitsForSlot(slot: Int): Int = bits shl (1 + slot * 3)
}
/**
* This transform determines the stability of every class, and synthesizes a StabilityInferred
* annotation on it, as well as putting a static final int of the stability to be used at runtime.
*/
class ClassStabilityTransformer(
private val useK2: Boolean,
context: IrPluginContext,
symbolRemapper: DeepCopySymbolRemapper,
metrics: ModuleMetrics,
stabilityInferencer: StabilityInferencer
) : AbstractComposeLowering(context, symbolRemapper, metrics, stabilityInferencer),
ClassLoweringPass,
ModuleLoweringPass {
private val StabilityInferredClass = getTopLevelClass(ComposeClassIds.StabilityInferred)
private val UNSTABLE = StabilityBits.UNSTABLE.bitsForSlot(0)
private val STABLE = StabilityBits.STABLE.bitsForSlot(0)
override fun lower(module: IrModuleFragment) {
module.transformChildrenVoid(this)
}
override fun lower(irClass: IrClass) {
}
override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid(this)
}
override fun visitClass(declaration: IrClass): IrStatement {
val result = super.visitClass(declaration)
val cls = result as? IrClass ?: return result
if (
(
// Including public AND internal to support incremental compilation, which
// is separated by file.
cls.visibility != DescriptorVisibilities.PUBLIC &&
cls.visibility != DescriptorVisibilities.INTERNAL
) ||
cls.isEnumClass ||
cls.isEnumEntry ||
cls.isInterface ||
cls.isAnnotationClass ||
cls.isAnonymousObject ||
cls.isExpect ||
cls.isInner ||
cls.isFileClass ||
cls.isCompanion ||
cls.defaultType.isInlineClassType()
) return cls
if (declaration.hasStableMarker()) {
metrics.recordClass(
declaration,
marked = true,
stability = Stability.Stable,
)
cls.addStabilityMarkerField(irConst(STABLE))
return cls
}
val stability = stabilityInferencer.stabilityOf(declaration.defaultType).normalize()
// remove type parameters
var parameterMask = 0
val stableExpr: IrExpression
if (cls.typeParameters.isNotEmpty()) {
val symbols = cls.typeParameters.map { it.symbol }
var externalParameters = false
stability.forEach {
when (it) {
is Stability.Parameter -> {
val index = symbols.indexOf(it.parameter.symbol)
if (index != -1) {
// the stability of this parameter matters for the stability of the
// class
parameterMask = parameterMask or (0b1 shl index)
} else {
externalParameters = true
}
}
else -> {
/* No action necessary */
}
}
}
if (stability.knownStable() && symbols.size < 32) {
parameterMask = parameterMask or (0b1 shl symbols.size)
}
stableExpr = if (externalParameters)
irConst(UNSTABLE)
else
stability.irStableExpression { irConst(STABLE) } ?: irConst(UNSTABLE)
} else {
stableExpr = stability.irStableExpression() ?: irConst(UNSTABLE)
if (stability.knownStable()) {
parameterMask = 0b1
}
}
metrics.recordClass(
declaration,
marked = false,
stability = stability
)
val annotation = IrConstructorCallImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
StabilityInferredClass.defaultType,
StabilityInferredClass.constructors.first(),
0,
0,
1,
null
).also {
it.putValueArgument(0, irConst(parameterMask))
}
if (useK2) {
context.annotationsRegistrar.addMetadataVisibleAnnotationsToElement(cls, annotation)
} else {
cls.annotations += annotation
}
cls.addStabilityMarkerField(stableExpr)
return result
}
private fun IrClass.addStabilityMarkerField(stabilityExpression: IrExpression) {
val stabilityField = makeStabilityField().apply {
parent = this@addStabilityMarkerField
initializer = IrExpressionBodyImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
stabilityExpression
)
}
if (context.platform.isJvm()) {
declarations += stabilityField
} else {
// This ensures proper mangles in k/js and k/native (since kotlin 1.6.0-rc2)
val stabilityProp = makeStabilityProp().apply {
parent = this@addStabilityMarkerField
backingField = stabilityField
}
stabilityField.correspondingPropertySymbol = stabilityProp.symbol
declarations += stabilityProp
}
}
}