[K2] Document KMP implementation in the K2 Compiler

Add comprehensive documentation explaining the implementation and
functions of the Kotlin multiplatform (KMP) support in the K2 Compiler.
This commit is contained in:
Simon Ogorodnik
2023-09-15 17:34:01 +02:00
committed by Space Team
parent 04c0cc5a5c
commit 024e94a479
12 changed files with 661 additions and 0 deletions
@@ -37,6 +37,14 @@ import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.utils.KotlinPaths
import java.io.File
/**
* This class is the entry-point for compiling Kotlin code into a metadata KLib.
*
* **Note: `2` in the name stands for Kotlin `TO` metadata compiler.
* This entry-point used by both K1 and K2.**
*
* Please see `/docs/fir/k2_kmp.md` for more info on the K2/FIR implementation.
*/
class K2MetadataCompiler : CLICompiler<K2MetadataCompilerArguments>() {
override val defaultPerformanceManager: CommonCompilerPerformanceManager = K2MetadataCompilerPerformanceManager()
@@ -9,6 +9,16 @@ import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
/**
* The main purpose of the lookup tag is to provide a reference to concrete classifier that
* allows obtaining FirClassifierSymbol within a given use-site session.
*
* During the deserialization we use lookup tags to avoid loading the entire class-type hierarchy.
*
* Use-site lookup of classifiers that are referenced by lookup tags allows type refinement that is needed for the expect/actual support.
*
* See `/docs/fir/k2_kmp.md`
*/
abstract class ConeClassifierLookupTag : TypeConstructorMarker {
abstract val name: Name
@@ -17,6 +27,9 @@ abstract class ConeClassifierLookupTag : TypeConstructorMarker {
}
}
/**
* @see ConeClassifierLookupTag
*/
abstract class ConeClassLikeLookupTag : ConeClassifierLookupTag() {
abstract val classId: ClassId
@@ -14,6 +14,13 @@ import org.jetbrains.kotlin.ir.util.IdSignatureComposer
import org.jetbrains.kotlin.ir.util.SymbolTable
import java.util.concurrent.ConcurrentHashMap
/**
* This storage contains the shared state of FIR2IR.
*
* State is shared between the conversions of different modules in the same platform compilation.
*
* See `/docs/fir/k2_kmp.md`
*/
class Fir2IrCommonMemberStorage(
signatureComposer: IdSignatureComposer,
firMangler: FirMangler
@@ -93,6 +93,27 @@ inline val FirBasedSymbol<*>.isJavaOrEnhancement: Boolean
private fun FirFunction.containsDefaultValue(index: Int): Boolean = valueParameters[index].defaultValue != null
/**
* Checks, if the value parameter has a default value w.r.t expect/actuals.
*
* Requires [FirResolvePhase.EXPECT_ACTUAL_MATCHING]
*
* In expect/actual declarations, the presence of default values can be determined by the expect declaration.
*
* ```kotlin
* // MODULE: common
* expect fun foo(bar: Int = 42)
*
* // MODULE: platform()(common)
* actual fun foo(bar: Int) { ... }
* ```
*
* See `/docs/fir/k2_kmp.md`
*
* @param index Index of the value parameter to check
* @return `true` if a parameter has defined default value, or if there is a default value defined on the expect declaration
* for this actual.
*/
fun FirFunction.itOrExpectHasDefaultParameterValue(index: Int): Boolean =
containsDefaultValue(index) || symbol.getSingleExpectForActualOrNull()?.fir?.containsDefaultValue(index) == true
@@ -29,6 +29,15 @@ import org.jetbrains.kotlin.types.ConstantValueKind
import org.jetbrains.kotlin.util.WeakPair
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
/**
* Main operation on the [ConeClassifierLookupTag]
*
* Lookups the tag into its target within the given [useSiteSession]
*
* The second step of type refinement, see `/docs/fir/k2_kmp.md`
*
* @see ConeClassifierLookupTag
*/
fun ConeClassifierLookupTag.toSymbol(useSiteSession: FirSession): FirClassifierSymbol<*>? =
when (this) {
is ConeClassLikeLookupTag -> toSymbol(useSiteSession)
@@ -37,6 +46,9 @@ fun ConeClassifierLookupTag.toSymbol(useSiteSession: FirSession): FirClassifierS
else -> null
}
/**
* @see toSymbol
*/
@OptIn(LookupTagInternals::class)
fun ConeClassLikeLookupTag.toSymbol(useSiteSession: FirSession): FirClassLikeSymbol<*>? {
if (this is ConeClassLookupTagWithFixedSymbol) {
@@ -49,6 +61,9 @@ fun ConeClassLikeLookupTag.toSymbol(useSiteSession: FirSession): FirClassLikeSym
}
}
/**
* @see toSymbol
*/
fun ConeClassLikeLookupTag.toFirRegularClassSymbol(session: FirSession): FirRegularClassSymbol? =
toSymbol(session) as? FirRegularClassSymbol
@@ -19,6 +19,17 @@ import org.jetbrains.kotlin.util.WeakPair
import org.jetbrains.kotlin.util.component1
import org.jetbrains.kotlin.util.component2
/**
* Compute the recursive type-alias expansion in the given type.
*
* A type of an expect class, that is actualized by a typealias will be expanded to the expansion of the typealias,
* when supplied with the session of actual.
*
* See `/docs/fir/k2_kmp.md`
*
* @param useSiteSession Session to be used for classifier lookups, see [toSymbol]
* @return Type, that is expanded to the concrete class type w.r.t to the [useSiteSession]
*/
fun ConeClassLikeType.fullyExpandedType(
useSiteSession: FirSession,
expandedConeType: (FirTypeAlias) -> ConeClassLikeType? = { alias ->
@@ -40,6 +51,9 @@ fun ConeClassLikeType.fullyExpandedType(
return fullyExpandedTypeNoCache(useSiteSession, expandedConeType)
}
/**
* @see fullyExpandedType
*/
fun ConeKotlinType.fullyExpandedType(
useSiteSession: FirSession
): ConeKotlinType = when (this) {
@@ -50,6 +64,9 @@ fun ConeKotlinType.fullyExpandedType(
else -> this
}
/**
* @see fullyExpandedType
*/
fun ConeSimpleKotlinType.fullyExpandedType(
useSiteSession: FirSession
): ConeSimpleKotlinType = when (this) {
@@ -167,6 +184,9 @@ private fun mapTypeAliasArguments(
return substitutor.substituteOrSelf(resultingType)
}
/**
* @see fullyExpandedType
*/
fun FirTypeAlias.fullyExpandedConeType(useSiteSession: FirSession): ConeClassLikeType? {
return expandedConeType?.fullyExpandedType(useSiteSession)
}
@@ -31,6 +31,14 @@ class FirExpectActualMatcherProcessor(
}
}
/**
* This transformer populates [expectForActual] mapping for actual declarations.
* Also, populates it [memberExpectForActual] mapping
*
* Should run before any kind of body resolution, since [expectForActual] is used there.
*
* See `/docs/fir/k2_kmp.md`
*/
open class FirExpectActualMatcherTransformer(
final override val session: FirSession,
private val scopeSession: ScopeSession,
@@ -17,12 +17,27 @@ private object ExpectForActualAttributeKey : FirDeclarationDataKey()
typealias ExpectForActualData = Map<ExpectActualCompatibility<FirBasedSymbol<*>>, List<FirBasedSymbol<*>>>
/**
* Actual declaration -> (many) expect declaration mapping. For top-level declarations.
*
* Populated by [FirResolvePhase.EXPECT_ACTUAL_MATCHING] phase for every top-level actual declaration.
*
* **Note: Mapping is computed from the declaration-site session of the actual declaration and not refined on the use-sites**
*
* See `/docs/fir/k2_kmp.md`
*/
@SymbolInternals
var FirDeclaration.expectForActual: ExpectForActualData? by FirDeclarationDataRegistry.data(ExpectForActualAttributeKey)
/**
* @see expectForActual
*/
fun FirFunctionSymbol<*>.getSingleExpectForActualOrNull(): FirFunctionSymbol<*>? =
(this as FirBasedSymbol<*>).getSingleExpectForActualOrNull() as? FirFunctionSymbol<*>
/**
* @see expectForActual
*/
fun FirBasedSymbol<*>.getSingleExpectForActualOrNull(): FirBasedSymbol<*>? {
val expectForActual = expectForActual ?: return null
val compatibleOrWeakCompatible: List<FirBasedSymbol<*>> =
@@ -30,6 +45,9 @@ fun FirBasedSymbol<*>.getSingleExpectForActualOrNull(): FirBasedSymbol<*>? {
return compatibleOrWeakCompatible.singleOrNull()
}
/**
* @see expectForActual
*/
val FirBasedSymbol<*>.expectForActual: ExpectForActualData?
get() {
lazyResolveToPhase(FirResolvePhase.EXPECT_ACTUAL_MATCHING)
@@ -45,4 +63,40 @@ typealias MemberExpectForActualData =
Map<Pair</* actual member */ FirBasedSymbol<*>, /* expect class */ FirRegularClassSymbol>,
Map</* expect member */ FirBasedSymbol<*>, ExpectActualCompatibility<*>>>
/**
* Actual class + expect class + actual member declaration -> (many) expect member declaration mapping.
*
* This attribute allows finding complex actualizations through type-aliases.
*
* ```kotlin
* // MODULE: common
* expect class A {
* fun foo() // expect.1
* }
* expect class B {
* fun foo() // expect.2
* }
*
* // MODULE: platform()(common)
*
* actual typealias A = Impl
* actual typealias B = Impl
*
* // attribute: memberExpectForActual
* // value: {
* // (symbol: expect class A, symbol: impl.1) => { symbol: expect.1 => Compatible },
* // (symbol: expect class B, symbol: impl.1) => { symbol: expect.2 => Compatible },
* // }
* class Impl {
* fun foo() {} // impl.1
* }
* ```
*
* Populated by [FirResolvePhase.EXPECT_ACTUAL_MATCHING] phase for every top-level class declaration that is either actual class or
* expansion of actual type-alias.
*
* **Note: Mapping is computed from the declaration-site session of the actual declaration and not refined on the use-sites**
*
* See `/docs/fir/k2_kmp.md`
*/
var FirRegularClass.memberExpectForActual: MemberExpectForActualData? by FirDeclarationDataRegistry.data(MemberExpectForActualAttributeKey)
@@ -7,6 +7,9 @@ package org.jetbrains.kotlin.fir.symbols
import org.jetbrains.kotlin.fir.symbols.impl.FirClassifierSymbol
/**
* @see ConeClassifierLookupTag
*/
abstract class ConeClassifierLookupTagWithFixedSymbol : ConeClassifierLookupTag() {
abstract val symbol: FirClassifierSymbol<*>
}
@@ -19,6 +19,13 @@ import org.jetbrains.kotlin.ir.util.*
data class IrActualizedResult(val actualizedExpectDeclarations: List<IrDeclaration>)
/**
* IrActualizer is responsible for performing actualization.
*
* The main action is the replacement of references to expect declarations with corresponding actuals on the IR level.
*
* See `/docs/fir/k2_kmp.md`
*/
object IrActualizer {
fun actualize(
mainFragment: IrModuleFragment,
@@ -23,6 +23,12 @@ import org.jetbrains.kotlin.utils.keysToMap
import org.jetbrains.kotlin.utils.zipIfSizesAreEqual
import java.util.*
/**
* This object is responsible for matching and checking of
* expect-actual pairs
*
* See `/docs/fir/k2_kmp.md` for details
*/
object AbstractExpectActualCompatibilityChecker {
fun <T : DeclarationSymbolMarker> getClassifiersCompatibility(
expectClassSymbol: RegularClassSymbolMarker,