[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:
committed by
Space Team
parent
04c0cc5a5c
commit
024e94a479
@@ -37,6 +37,14 @@ import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
|
|||||||
import org.jetbrains.kotlin.utils.KotlinPaths
|
import org.jetbrains.kotlin.utils.KotlinPaths
|
||||||
import java.io.File
|
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>() {
|
class K2MetadataCompiler : CLICompiler<K2MetadataCompilerArguments>() {
|
||||||
|
|
||||||
override val defaultPerformanceManager: CommonCompilerPerformanceManager = K2MetadataCompilerPerformanceManager()
|
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.name.Name
|
||||||
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
|
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 class ConeClassifierLookupTag : TypeConstructorMarker {
|
||||||
abstract val name: Name
|
abstract val name: Name
|
||||||
|
|
||||||
@@ -17,6 +27,9 @@ abstract class ConeClassifierLookupTag : TypeConstructorMarker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see ConeClassifierLookupTag
|
||||||
|
*/
|
||||||
abstract class ConeClassLikeLookupTag : ConeClassifierLookupTag() {
|
abstract class ConeClassLikeLookupTag : ConeClassifierLookupTag() {
|
||||||
abstract val classId: ClassId
|
abstract val classId: ClassId
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ import org.jetbrains.kotlin.ir.util.IdSignatureComposer
|
|||||||
import org.jetbrains.kotlin.ir.util.SymbolTable
|
import org.jetbrains.kotlin.ir.util.SymbolTable
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
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(
|
class Fir2IrCommonMemberStorage(
|
||||||
signatureComposer: IdSignatureComposer,
|
signatureComposer: IdSignatureComposer,
|
||||||
firMangler: FirMangler
|
firMangler: FirMangler
|
||||||
|
|||||||
@@ -93,6 +93,27 @@ inline val FirBasedSymbol<*>.isJavaOrEnhancement: Boolean
|
|||||||
|
|
||||||
private fun FirFunction.containsDefaultValue(index: Int): Boolean = valueParameters[index].defaultValue != null
|
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 =
|
fun FirFunction.itOrExpectHasDefaultParameterValue(index: Int): Boolean =
|
||||||
containsDefaultValue(index) || symbol.getSingleExpectForActualOrNull()?.fir?.containsDefaultValue(index) == true
|
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.util.WeakPair
|
||||||
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
|
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<*>? =
|
fun ConeClassifierLookupTag.toSymbol(useSiteSession: FirSession): FirClassifierSymbol<*>? =
|
||||||
when (this) {
|
when (this) {
|
||||||
is ConeClassLikeLookupTag -> toSymbol(useSiteSession)
|
is ConeClassLikeLookupTag -> toSymbol(useSiteSession)
|
||||||
@@ -37,6 +46,9 @@ fun ConeClassifierLookupTag.toSymbol(useSiteSession: FirSession): FirClassifierS
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see toSymbol
|
||||||
|
*/
|
||||||
@OptIn(LookupTagInternals::class)
|
@OptIn(LookupTagInternals::class)
|
||||||
fun ConeClassLikeLookupTag.toSymbol(useSiteSession: FirSession): FirClassLikeSymbol<*>? {
|
fun ConeClassLikeLookupTag.toSymbol(useSiteSession: FirSession): FirClassLikeSymbol<*>? {
|
||||||
if (this is ConeClassLookupTagWithFixedSymbol) {
|
if (this is ConeClassLookupTagWithFixedSymbol) {
|
||||||
@@ -49,6 +61,9 @@ fun ConeClassLikeLookupTag.toSymbol(useSiteSession: FirSession): FirClassLikeSym
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see toSymbol
|
||||||
|
*/
|
||||||
fun ConeClassLikeLookupTag.toFirRegularClassSymbol(session: FirSession): FirRegularClassSymbol? =
|
fun ConeClassLikeLookupTag.toFirRegularClassSymbol(session: FirSession): FirRegularClassSymbol? =
|
||||||
toSymbol(session) as? 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.component1
|
||||||
import org.jetbrains.kotlin.util.component2
|
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(
|
fun ConeClassLikeType.fullyExpandedType(
|
||||||
useSiteSession: FirSession,
|
useSiteSession: FirSession,
|
||||||
expandedConeType: (FirTypeAlias) -> ConeClassLikeType? = { alias ->
|
expandedConeType: (FirTypeAlias) -> ConeClassLikeType? = { alias ->
|
||||||
@@ -40,6 +51,9 @@ fun ConeClassLikeType.fullyExpandedType(
|
|||||||
return fullyExpandedTypeNoCache(useSiteSession, expandedConeType)
|
return fullyExpandedTypeNoCache(useSiteSession, expandedConeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see fullyExpandedType
|
||||||
|
*/
|
||||||
fun ConeKotlinType.fullyExpandedType(
|
fun ConeKotlinType.fullyExpandedType(
|
||||||
useSiteSession: FirSession
|
useSiteSession: FirSession
|
||||||
): ConeKotlinType = when (this) {
|
): ConeKotlinType = when (this) {
|
||||||
@@ -50,6 +64,9 @@ fun ConeKotlinType.fullyExpandedType(
|
|||||||
else -> this
|
else -> this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see fullyExpandedType
|
||||||
|
*/
|
||||||
fun ConeSimpleKotlinType.fullyExpandedType(
|
fun ConeSimpleKotlinType.fullyExpandedType(
|
||||||
useSiteSession: FirSession
|
useSiteSession: FirSession
|
||||||
): ConeSimpleKotlinType = when (this) {
|
): ConeSimpleKotlinType = when (this) {
|
||||||
@@ -167,6 +184,9 @@ private fun mapTypeAliasArguments(
|
|||||||
return substitutor.substituteOrSelf(resultingType)
|
return substitutor.substituteOrSelf(resultingType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see fullyExpandedType
|
||||||
|
*/
|
||||||
fun FirTypeAlias.fullyExpandedConeType(useSiteSession: FirSession): ConeClassLikeType? {
|
fun FirTypeAlias.fullyExpandedConeType(useSiteSession: FirSession): ConeClassLikeType? {
|
||||||
return expandedConeType?.fullyExpandedType(useSiteSession)
|
return expandedConeType?.fullyExpandedType(useSiteSession)
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -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(
|
open class FirExpectActualMatcherTransformer(
|
||||||
final override val session: FirSession,
|
final override val session: FirSession,
|
||||||
private val scopeSession: ScopeSession,
|
private val scopeSession: ScopeSession,
|
||||||
|
|||||||
@@ -17,12 +17,27 @@ private object ExpectForActualAttributeKey : FirDeclarationDataKey()
|
|||||||
|
|
||||||
typealias ExpectForActualData = Map<ExpectActualCompatibility<FirBasedSymbol<*>>, List<FirBasedSymbol<*>>>
|
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
|
@SymbolInternals
|
||||||
var FirDeclaration.expectForActual: ExpectForActualData? by FirDeclarationDataRegistry.data(ExpectForActualAttributeKey)
|
var FirDeclaration.expectForActual: ExpectForActualData? by FirDeclarationDataRegistry.data(ExpectForActualAttributeKey)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see expectForActual
|
||||||
|
*/
|
||||||
fun FirFunctionSymbol<*>.getSingleExpectForActualOrNull(): FirFunctionSymbol<*>? =
|
fun FirFunctionSymbol<*>.getSingleExpectForActualOrNull(): FirFunctionSymbol<*>? =
|
||||||
(this as FirBasedSymbol<*>).getSingleExpectForActualOrNull() as? FirFunctionSymbol<*>
|
(this as FirBasedSymbol<*>).getSingleExpectForActualOrNull() as? FirFunctionSymbol<*>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see expectForActual
|
||||||
|
*/
|
||||||
fun FirBasedSymbol<*>.getSingleExpectForActualOrNull(): FirBasedSymbol<*>? {
|
fun FirBasedSymbol<*>.getSingleExpectForActualOrNull(): FirBasedSymbol<*>? {
|
||||||
val expectForActual = expectForActual ?: return null
|
val expectForActual = expectForActual ?: return null
|
||||||
val compatibleOrWeakCompatible: List<FirBasedSymbol<*>> =
|
val compatibleOrWeakCompatible: List<FirBasedSymbol<*>> =
|
||||||
@@ -30,6 +45,9 @@ fun FirBasedSymbol<*>.getSingleExpectForActualOrNull(): FirBasedSymbol<*>? {
|
|||||||
return compatibleOrWeakCompatible.singleOrNull()
|
return compatibleOrWeakCompatible.singleOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see expectForActual
|
||||||
|
*/
|
||||||
val FirBasedSymbol<*>.expectForActual: ExpectForActualData?
|
val FirBasedSymbol<*>.expectForActual: ExpectForActualData?
|
||||||
get() {
|
get() {
|
||||||
lazyResolveToPhase(FirResolvePhase.EXPECT_ACTUAL_MATCHING)
|
lazyResolveToPhase(FirResolvePhase.EXPECT_ACTUAL_MATCHING)
|
||||||
@@ -45,4 +63,40 @@ typealias MemberExpectForActualData =
|
|||||||
Map<Pair</* actual member */ FirBasedSymbol<*>, /* expect class */ FirRegularClassSymbol>,
|
Map<Pair</* actual member */ FirBasedSymbol<*>, /* expect class */ FirRegularClassSymbol>,
|
||||||
Map</* expect member */ FirBasedSymbol<*>, ExpectActualCompatibility<*>>>
|
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)
|
var FirRegularClass.memberExpectForActual: MemberExpectForActualData? by FirDeclarationDataRegistry.data(MemberExpectForActualAttributeKey)
|
||||||
|
|||||||
+3
@@ -7,6 +7,9 @@ package org.jetbrains.kotlin.fir.symbols
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassifierSymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirClassifierSymbol
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see ConeClassifierLookupTag
|
||||||
|
*/
|
||||||
abstract class ConeClassifierLookupTagWithFixedSymbol : ConeClassifierLookupTag() {
|
abstract class ConeClassifierLookupTagWithFixedSymbol : ConeClassifierLookupTag() {
|
||||||
abstract val symbol: FirClassifierSymbol<*>
|
abstract val symbol: FirClassifierSymbol<*>
|
||||||
}
|
}
|
||||||
+7
@@ -19,6 +19,13 @@ import org.jetbrains.kotlin.ir.util.*
|
|||||||
|
|
||||||
data class IrActualizedResult(val actualizedExpectDeclarations: List<IrDeclaration>)
|
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 {
|
object IrActualizer {
|
||||||
fun actualize(
|
fun actualize(
|
||||||
mainFragment: IrModuleFragment,
|
mainFragment: IrModuleFragment,
|
||||||
|
|||||||
+6
@@ -23,6 +23,12 @@ import org.jetbrains.kotlin.utils.keysToMap
|
|||||||
import org.jetbrains.kotlin.utils.zipIfSizesAreEqual
|
import org.jetbrains.kotlin.utils.zipIfSizesAreEqual
|
||||||
import java.util.*
|
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 {
|
object AbstractExpectActualCompatibilityChecker {
|
||||||
fun <T : DeclarationSymbolMarker> getClassifiersCompatibility(
|
fun <T : DeclarationSymbolMarker> getClassifiersCompatibility(
|
||||||
expectClassSymbol: RegularClassSymbolMarker,
|
expectClassSymbol: RegularClassSymbolMarker,
|
||||||
|
|||||||
@@ -0,0 +1,499 @@
|
|||||||
|
# K2 KMP (Kotlin multiplatform) implementation documentation
|
||||||
|
|
||||||
|
This document describes the implementation of the KMP support in the K2 Compiler along components.
|
||||||
|
|
||||||
|
**Note**: The current compilation model is temporary, and should be changed to final in [KT-57327](https://youtrack.jetbrains.com/issue/KT-57327)
|
||||||
|
|
||||||
|
## Document structure
|
||||||
|
|
||||||
|
- [Glossary](#glossary)
|
||||||
|
- [Metadata Compilation](#metadata-compilation)
|
||||||
|
- [Platform Compilation](#platform-compilation)
|
||||||
|
- [FIR2IR](#fir2ir)
|
||||||
|
- [IRActualizer](#iractualizer)
|
||||||
|
- [Fake overrides](#fake-overrides)
|
||||||
|
- [Frontend support for expect/actual](#frontend-support-for-expectactual)
|
||||||
|
- [Type refinement](#type-refinement)
|
||||||
|
- [Default propagation](#default-propagation)
|
||||||
|
- [Actual -> expect binding construction](#fir-actual---expect-binding-construction)
|
||||||
|
- [Frontend checkers](#frontend-checkers)
|
||||||
|
|
||||||
|
## Glossary
|
||||||
|
|
||||||
|
### Source-set
|
||||||
|
|
||||||
|
A bunch of source files along with its depends-on relations with other source-sets.
|
||||||
|
|
||||||
|
See: https://kotlinlang.org/docs/multiplatform-discover-project.html#source-sets
|
||||||
|
|
||||||
|
**Note:** Binary dependencies are provided on the [compilation](#compilation) level and not considered a part of the source-set definition
|
||||||
|
by the compiler.
|
||||||
|
|
||||||
|
### Module
|
||||||
|
|
||||||
|
A compiler module, entity with source-code and dependencies.
|
||||||
|
- One-to-one mapped to the corresponding FirSession.
|
||||||
|
- One-to-one mapped to the FirModuleData.
|
||||||
|
- Compiler representation of a [source-set](#source-set)
|
||||||
|
|
||||||
|
In a [platform compilation](#platform-compilation),
|
||||||
|
[multiple modules are created from the input](#source-set-and-module-relation) [source-sets](#source-set).
|
||||||
|
|
||||||
|
### Compilation
|
||||||
|
|
||||||
|
A single invocation of the compiler entry-point
|
||||||
|
|
||||||
|
### Metadata dependencies/KLibs
|
||||||
|
|
||||||
|
- KLib that only contains frontend metadata, without IR. It can only be used to analyze dependent code.
|
||||||
|
|
||||||
|
### HMPP
|
||||||
|
|
||||||
|
Hierarchical multi-platform projects
|
||||||
|
- See https://kotlinlang.org/docs/multiplatform-hierarchy.html
|
||||||
|
|
||||||
|
### Actualization
|
||||||
|
|
||||||
|
The process of replacing references to expect declarations with corresponding actual declarations.
|
||||||
|
See [IRActualizer](#iractualizer)
|
||||||
|
|
||||||
|
### actual->expect binding
|
||||||
|
|
||||||
|
A relation between two declarations that form an expect-actual pair.
|
||||||
|
- For classes: defined as identical ClassId.
|
||||||
|
- For callables: defined as complex matching rule, which is similar to overloading.
|
||||||
|
|
||||||
|
In general case, the relation is many-to-many.
|
||||||
|
However, in correct code it is either one-to-one, or one-to-many in case of type-aliases.
|
||||||
|
|
||||||
|
See [FIR: actual -> expect binding construction](#fir-actual---expect-binding-construction)
|
||||||
|
|
||||||
|
### Common source-set
|
||||||
|
|
||||||
|
A [source-set] that may contain expect declarations.
|
||||||
|
As opposed to a platform [source-sets](#source-set) that must only contain actual or regular declarations.
|
||||||
|
|
||||||
|
Common source-sets usually contain code that is shared across different targets, and by that is included
|
||||||
|
in multiple [platform compilations](#platform-compilation).
|
||||||
|
|
||||||
|
However, one may define [HMPP](#hmpp) hierarchy with only one target while still having common source-sets.
|
||||||
|
|
||||||
|
## Metadata Compilation
|
||||||
|
|
||||||
|
- Aimed to check code platform-independent compilation.
|
||||||
|
- Each [common source set](#common-source-set) is compiled with its metadata binary dependencies.
|
||||||
|
- Artifacts aren't used for subsequent platform compilations but for the IDE and other metadata compilations.
|
||||||
|
- In this compilation, we analyze code as if it is fully isolated from the corresponding actualizations.
|
||||||
|
- Doing so will allow us to check that code can be compiled for multiple targets.
|
||||||
|
- Only the [common source-set](#common-source-set) and its [metadata binary dependencies](#metadata-dependenciesklibs) are taken into account.
|
||||||
|
- No access to the target-specific dependencies or source-sets.
|
||||||
|
- Source-sets from depends-on are provided as [metadata binary dependencies](#metadata-dependenciesklibs) in the form of unpacked klib
|
||||||
|
- See `-Xrefines-path`
|
||||||
|
|
||||||
|
**Entry-point:** [`org.jetbrains.kotlin.cli.metadata.K2MetadataCompiler`](../../compiler/cli/src/org/jetbrains/kotlin/cli/metadata/K2MetadataCompiler.kt)
|
||||||
|
|
||||||
|
Also, see: [`org.jetbrains.kotlin.cli.metadata.FirMetadataSerializer`](../../compiler/cli/src/org/jetbrains/kotlin/cli/metadata/FirMetadataSerializer.kt)
|
||||||
|
|
||||||
|
**Inputs:**
|
||||||
|
- [Metadata KLibs for dependencies](#metadata-dependenciesklibs)
|
||||||
|
- [Metadata KLibs for dependsOn source-sets](#metadata-dependenciesklibs)
|
||||||
|
- Source files of a single [common source-set](#common-source-set), such as "commonMain"
|
||||||
|
|
||||||
|
**Outputs:**
|
||||||
|
- [Metadata KLibs](#metadata-dependenciesklibs)
|
||||||
|
|
||||||
|
In this mode, we analyze only common sources.
|
||||||
|
This compilation mode is very similar to a usual compilation pipeline with the exception that no IR is serialized to the output.
|
||||||
|
|
||||||
|
Actual-expect [matching](#frontend-support-for-expectactual) and [checking](#frontend-checkers) are performed,
|
||||||
|
since in [HMPP](#hmpp) we can have actuals in metadata compilation inputs.
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** The full set of actual declarations can't be determined at that point.
|
||||||
|
The only actual -> expect binding could be obtained and checked.
|
||||||
|
|
||||||
|
**Note:** In this compilation mode [actual->expect binding](#actual-expect-binding) may point to a deserialized element,
|
||||||
|
since expect declarations from depends-on source-sets will be present as [metadata binary dependencies](#metadata-dependenciesklibs).
|
||||||
|
|
||||||
|
## Platform Compilation
|
||||||
|
|
||||||
|
- Invoked per each target.
|
||||||
|
- Input includes [source-sets](#source-set) from entire target depends-on graph.
|
||||||
|
- Outputs a platform artifact (jar file or klib).
|
||||||
|
- [Source-sets](#source-set) form a directed acyclic graph.
|
||||||
|
- The graph must be terminated by one leaf [source-set](#source-set)
|
||||||
|
- Compilation requires a platform classpath. See: [binary dependencies](#platform-compilation-binary-dependencies)
|
||||||
|
|
||||||
|
Please consult with https://kotlinlang.org/docs/multiplatform-hierarchy.html for more info on source-set structure.
|
||||||
|
|
||||||
|
This is the main part where the pipeline diverges from simple compilation.
|
||||||
|
|
||||||
|
Ex:
|
||||||
|
```kotlin
|
||||||
|
// MODULE: common
|
||||||
|
expect fun a1(): String
|
||||||
|
expect fun a2(): String
|
||||||
|
|
||||||
|
// MODULE: intermediate()()(common)
|
||||||
|
actual fun a1(): String = ""
|
||||||
|
expect fun a4(): String
|
||||||
|
expect fun a5(): String
|
||||||
|
|
||||||
|
// MODULE: unrelated
|
||||||
|
expect fun a3(): String
|
||||||
|
expect fun a5(): String
|
||||||
|
|
||||||
|
// MODULE: platform()()(intermediate, unrelated)
|
||||||
|
actual fun a2(): String = ""
|
||||||
|
actual fun a3(): String = ""
|
||||||
|
actual fun a4(): String = ""
|
||||||
|
actual fun a5(): String = "" // ambiguous
|
||||||
|
```
|
||||||
|
|
||||||
|
In the given example, modules aka source-sets form the following dependency graph:
|
||||||
|
```
|
||||||
|
common
|
||||||
|
^
|
||||||
|
|
|
||||||
|
intermediate unrelated
|
||||||
|
^ ^
|
||||||
|
| /
|
||||||
|
| /
|
||||||
|
platform
|
||||||
|
```
|
||||||
|
|
||||||
|
### Source-set and module relation
|
||||||
|
|
||||||
|
During the platform compilation, the entire [source-set](#source-set) graph is passed to the compiler CLI.
|
||||||
|
|
||||||
|
See: `-Xfragments`, `-Xfragment-sources`, `-Xfragment-refines`.
|
||||||
|
|
||||||
|
When combined with [binary dependencies](#platform-compilation-binary-dependencies) passed, it allows constructing graph of
|
||||||
|
compiler [modules](#module).
|
||||||
|
|
||||||
|
Compiler [module](#module) is created for each passed [source-set](#source-set).
|
||||||
|
|
||||||
|
### Platform compilation binary dependencies
|
||||||
|
|
||||||
|
The K2 uses shared **platform binary dependencies** for analysis of **all** source-sets in the platform compilation.
|
||||||
|
|
||||||
|
The key reason for that is that we are unable to provide full KLibs for [common source-sets](#common-source-set) yet.
|
||||||
|
|
||||||
|
Observed behavior is the following:
|
||||||
|
```kotlin
|
||||||
|
// MODULE: common_dep
|
||||||
|
// library: dep
|
||||||
|
fun foo(a: Any) {} // (dep.1)
|
||||||
|
|
||||||
|
// MODULE: platform_dep
|
||||||
|
// library: dep
|
||||||
|
fun foo(a: String) {} // (dep.2)
|
||||||
|
|
||||||
|
// MODULE: common
|
||||||
|
// dependencies { implementation("dep") }
|
||||||
|
fun bar(a: Any) {} // (a.1)
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
bar("") // (a.1)
|
||||||
|
foo("") // (dep.2) !!! While platform compilation for this module
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODULE: platform
|
||||||
|
// depends-on: common
|
||||||
|
fun bar(a: String) {} // (a.2)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Platform compilation pipeline
|
||||||
|
|
||||||
|
Given that, source analysis order is defined as the following:
|
||||||
|
|
||||||
|
Analyze from the most common module to the platform modules.
|
||||||
|
So that all `depends-on` modules of the module are analyzed before the module itself.
|
||||||
|
|
||||||
|
In order to achieve it, [modules](#module) are sorted topologically over depends-on relation graph.
|
||||||
|
|
||||||
|
Platform compilation pipeline consists of the following steps:
|
||||||
|
1. For each [module](#module) in source analysis order
|
||||||
|
- [Use a shared set of binary dependencies](#platform-compilation-binary-dependencies)
|
||||||
|
- [Run the frontend, store FIR representation.](#frontend-support-for-expectactual)
|
||||||
|
- Use FIR representations of depends on modules that were obtained before as dependencies.
|
||||||
|
- *[Resolution requires knowledge of actual->expect binding.](#fir-actual---expect-binding-construction)
|
||||||
|
- [Run the frontend checkers for module FIR.](#frontend-checkers)
|
||||||
|
- [Run the FIR2IR, store the resulting IR module fragment.](#fir2ir)
|
||||||
|
- *[Re-use the FIR2IR state](#fir2ir-shared-state)
|
||||||
|
2. [Combine all resulting IR module fragments](#iractualizer)
|
||||||
|
- Perform [actualization](#actualization)
|
||||||
|
- Check that every expect declaration has corresponding actual and was actualized.
|
||||||
|
- *Reconstruct fake-overrides.
|
||||||
|
- Remove expect declarations.
|
||||||
|
- Merge IR module fragments into last fragment
|
||||||
|
- Now, we have complete IR for module that doesn't reference any expects and could be passed to backend.
|
||||||
|
3. Pass the resulting IR to the backend or serialize it into KLib.
|
||||||
|
- Backend compiler plugins are invoked before running the backend
|
||||||
|
|
||||||
|
### FIR2IR
|
||||||
|
|
||||||
|
FIR2IR is applied to the modules in the topological order.
|
||||||
|
|
||||||
|
**Inputs:**
|
||||||
|
- FIR of one of the [modules](#module)
|
||||||
|
- [Compilation shared state](#fir2ir-shared-state)
|
||||||
|
|
||||||
|
**Outputs:**
|
||||||
|
- IR for the module
|
||||||
|
- Mutation of [shared state](#fir2ir-shared-state)
|
||||||
|
|
||||||
|
#### FIR2IR shared state
|
||||||
|
|
||||||
|
The FIR2IR state, represented by `Fir2IrCommonMemberStorage` is shared across entire [platform compilation](#platform-compilation).
|
||||||
|
|
||||||
|
See: [`org.jetbrains.kotlin.fir.backend.Fir2IrCommonMemberStorage`](../../compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrCommonMemberStorage.kt)
|
||||||
|
|
||||||
|
Since FIR2IR is invoked over each [module](#module) over depends-on graph,
|
||||||
|
we need to avoid creating IR for declarations in a [common source-sets](#common-source-set) multiple times.
|
||||||
|
|
||||||
|
The same applies to the declarations from [binary dependencies](#platform-compilation-binary-dependencies), which are shared among the entire compilation.
|
||||||
|
|
||||||
|
Therefore, FIR2IR uses shared storage for get-or-create operations for declarations.
|
||||||
|
|
||||||
|
### IRActualizer
|
||||||
|
|
||||||
|
The IR actualizer is a component performing [actualization](#actualization) over IR
|
||||||
|
|
||||||
|
See: [`org.jetbrains.kotlin.backend.common.actualizer.IrActualizer`](../../compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrActualizer.kt)
|
||||||
|
|
||||||
|
In the current model, IR actualizer is used during the [platform compilation](#platform-compilation), to produce complete IR that is correct
|
||||||
|
from the backend standpoint.
|
||||||
|
|
||||||
|
**Inputs:**
|
||||||
|
- IR for each of the modules.
|
||||||
|
- IR might contain expect declarations and references to it.
|
||||||
|
|
||||||
|
**Outputs:**
|
||||||
|
- Single IR module.
|
||||||
|
- No expect declarations in the IR.
|
||||||
|
- IR is in the usual state, as for non-multiplatform projects
|
||||||
|
|
||||||
|
**Constraints:**
|
||||||
|
- No access to FIR or frontend state allowed
|
||||||
|
- Must operate over IR
|
||||||
|
- Diagnostic reporting is complicated and won't work for the IDE without special support
|
||||||
|
|
||||||
|
The following actions are preformed:
|
||||||
|
- Expect -> actual binding is constructed
|
||||||
|
- We have all expects and that's why we can construct it as opposed to the [actual -> expect binding](#actual-expect-binding)
|
||||||
|
- We can report diagnostics at that moment
|
||||||
|
- Top-level expect declarations are detached from IrModuleFragment
|
||||||
|
- Member expect declarations can only be contained within top-level expects, and is detached together with its container
|
||||||
|
- Modulo [`@OptionalExpectation`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-optional-expectation/)
|
||||||
|
- IR for default values is copied from expects to actuals. See [default propagation](#default-propagation) for the frontend part of work.
|
||||||
|
- All module fragments are merged into one
|
||||||
|
- [Fake-override actualization is performed](#fake-overrides)
|
||||||
|
|
||||||
|
### Fake overrides
|
||||||
|
|
||||||
|
FIR doesn't use fake-overrides in the frontend, and that's why we construct them during FIR2IR in order to match backend expectations
|
||||||
|
By that, we require special handling of fake-overrides during the [platform compilation](#platform-compilation).
|
||||||
|
|
||||||
|
during the analysis and couldn't construct fake-overrides properly.
|
||||||
|
|
||||||
|
Ex:
|
||||||
|
```kotlin
|
||||||
|
// MODULE: common
|
||||||
|
expect class A
|
||||||
|
expect class B
|
||||||
|
|
||||||
|
|
||||||
|
interface I {
|
||||||
|
fun foo(q: A)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface J {
|
||||||
|
fun foo(q: B)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface K : I, J {
|
||||||
|
// FIR2IR F/O fun foo(q: A) (1)
|
||||||
|
// FIR2IR F/O fun foo(q: B) (2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODULE: platform()()(common)
|
||||||
|
|
||||||
|
actual typealias A = Int
|
||||||
|
actual typealias B = Int
|
||||||
|
|
||||||
|
class Impl : K {
|
||||||
|
override fun foo(q: Int) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
In the given example during the actualization, we need to combine (1) and (2) into one fake-override.
|
||||||
|
|
||||||
|
## Frontend support for expect/actual
|
||||||
|
|
||||||
|
During the resolution of each module, a special set of measures is implemented to allow proper resolution.
|
||||||
|
|
||||||
|
### Type refinement
|
||||||
|
|
||||||
|
Type refinement is a process of obtaining use-site view to the declaration.
|
||||||
|
Since we re-use FIR of each module during the analysis of its dependants, we need to perform
|
||||||
|
type refinement.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// MODULE: common
|
||||||
|
expect class Foo
|
||||||
|
|
||||||
|
val foo: Foo = TODO()
|
||||||
|
// declaration-site type: expect class Foo
|
||||||
|
|
||||||
|
// MODULE: platform()()(common)
|
||||||
|
actual class Foo {
|
||||||
|
fun bar() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo.bar() // use-site type: actual class Foo
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**It is possible due to the following principles:**
|
||||||
|
- It isn't possible to obtain the scope of a type without knowing the use-site
|
||||||
|
- It isn't possible to obtain the type declaring class without knowing the use-site
|
||||||
|
- Every type is subject to the refinement before use
|
||||||
|
- ClassId can only be used to identify class within one module
|
||||||
|
|
||||||
|
Refinement of a type happens in two stages:
|
||||||
|
1. `ConeKotlinType.fullyExpandedType(useSiteSession: FirSession): ConeKotlinType`
|
||||||
|
- At that stage, we unwrap any type-aliases in the type using useSiteSession as a point of view.
|
||||||
|
2. `ConeClassifierLookupTag.toSymbol(useSiteSession: FirSession): FirClassifierSymbol<*>`
|
||||||
|
- All classifier types contain lookupTag inside them.
|
||||||
|
- To obtain reference to the type declaring class, one should call toSymbol on it providing the useSiteSession as a point of view.
|
||||||
|
- Then, the lookup of the class in the use-site module will be performed based on the class id stored within the lookup tag.
|
||||||
|
|
||||||
|
**WARNING:** Type refinement is also needed for general dependency substitution algorithm, such as classpath order substitution.
|
||||||
|
|
||||||
|
**It works for expect/actual classes only because [actual->expect binding](#actual-expect-binding) is defined as a matching of ClassId**
|
||||||
|
|
||||||
|
### Default propagation
|
||||||
|
|
||||||
|
We need to know a set of arguments that should be provided for a call to resolve it due to the Kotlin resolution and overloading rules.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// MODULE: common
|
||||||
|
expect fun foo(a: String = "")
|
||||||
|
expect class Bar {
|
||||||
|
fun buz(a: String = "")
|
||||||
|
}
|
||||||
|
// MODULE: intermediate()()(common)
|
||||||
|
actual fun foo(a: String) {}
|
||||||
|
actual class Bar {
|
||||||
|
fun buz(a: String) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo() // use-site
|
||||||
|
Bar().buz() // use-site
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the given example, we need to know [actual->expect binding](#actual-expect-binding) in order to resolve use-site calls.
|
||||||
|
|
||||||
|
As during the resolution on the use-site, we will resolve to the actual declarations, we need to obtain its default argument positions from
|
||||||
|
actuals.
|
||||||
|
|
||||||
|
### FIR: actual -> expect binding construction
|
||||||
|
|
||||||
|
We compute that binding by analyzing module with `FirExpectActualMatcherTransformer`.
|
||||||
|
|
||||||
|
See: [`org.jetbrains.kotlin.fir.resolve.transformers.mpp.FirExpectActualMatcherTransformer`](../../compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatcherTransformer.kt)
|
||||||
|
|
||||||
|
**The responsibility** of this phase is to perform [expect/actual matching](#matching-vs-checking).
|
||||||
|
|
||||||
|
This phase happens before body/implicit type resolution
|
||||||
|
as the binding is required to perform [call resolution in case of defaults](#default-propagation)
|
||||||
|
|
||||||
|
Binding is stored in the `FirDeclaration.expectForActual` attribute and allow to determine expects that corresponds to the
|
||||||
|
actual declaration during the analysis of `intermediate` module.
|
||||||
|
|
||||||
|
**Hard-constraint:**
|
||||||
|
`FirExpectActualMatcherTransformer` cannot use return types of declarations to bind actual with expect.
|
||||||
|
|
||||||
|
Since the return type of actual function might be not yet resolved before the implicit type body resolve phase.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// MODULE: common
|
||||||
|
expect fun foo(a: String = "")
|
||||||
|
|
||||||
|
// MODULE: platform()()(common)
|
||||||
|
actual fun foo(a: String) = run {
|
||||||
|
foo() // In order to resolve this call, we need to have actual -> expect binding
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Explanation:** While it is possible to compute the binding on-demand and remove this constraint,
|
||||||
|
we chose to have it as a separate phase to avoid further complication of body resolve.
|
||||||
|
|
||||||
|
There are no overloads by return type in Kotlin, and it makes it possible to avoid return type matching as a part of
|
||||||
|
[actual->expect binding construction](#fir-actual---expect-binding-construction).
|
||||||
|
However, we still [check](#frontend-checkers) that return type matches afterward.
|
||||||
|
|
||||||
|
#### Matching vs checking
|
||||||
|
|
||||||
|
Currently, both matching and a part of checking are performed in the `AbstractExpectActualCompatibilityChecker`.
|
||||||
|
|
||||||
|
See: [`org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualCompatibilityChecker`](../../compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt)
|
||||||
|
|
||||||
|
Matching is a process of finding expect-actual pairs.
|
||||||
|
For classes, it is matching of class-ids.
|
||||||
|
For callables, it is a complex rule, similar to overloading.
|
||||||
|
If declarations don't match, no pair is formed and **matching continues** over other declarations.
|
||||||
|
|
||||||
|
Checking is a process of checking that a pair is correct w.r.t all compatibility requirements.
|
||||||
|
If checking failed for a pair, error is reported for the pair.
|
||||||
|
It will fail compilation.
|
||||||
|
|
||||||
|
## Frontend checkers
|
||||||
|
|
||||||
|
Frontend checkers are executed in the context of each module (use-site), after its resolution.
|
||||||
|
|
||||||
|
**Inputs:**
|
||||||
|
- FIR of one of the [modules](#module)
|
||||||
|
- It can contain both expects and actuals
|
||||||
|
|
||||||
|
**Constraints:**
|
||||||
|
- The full set of actuals aren't known yet
|
||||||
|
- [Actual -> expect binding](#actual-expect-binding) is available
|
||||||
|
- [Limitations](#limitations)
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
|
||||||
|
The key limitation is the fact that frontend checkers are run in the context of declaration site.
|
||||||
|
|
||||||
|
Thus, it is impossible to observe member scopes of classes with respect to actualization since the corresponding actuals
|
||||||
|
are contained in further modules.
|
||||||
|
|
||||||
|
Type checks can also be performed with knowledge available on expect declaration site.
|
||||||
|
|
||||||
|
In order to reduce the need to perform additional checks on the backend and in the [IRActualizer](#iractualizer) it is advised to
|
||||||
|
follow the [LSP](https://en.wikipedia.org/wiki/Liskov_substitution_principle) in the design.
|
||||||
|
|
||||||
|
By that, meaning that actual declaration must be as compatible with the corresponding expect declaration, as that
|
||||||
|
it could replace the expect declaration, without the appearance of errors.
|
||||||
|
|
||||||
|
**Note:** There are compiler checks that violate LSP to the extent that it is reported on certain leaf types.
|
||||||
|
It is expected that such checks won't trigger in case of actualization.
|
||||||
|
|
||||||
|
E.g:
|
||||||
|
```kotlin
|
||||||
|
// MODULE: common
|
||||||
|
expect class E()
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
E() // It's OK, since it isn't deprecated in common
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODULE: platform()()(common)
|
||||||
|
@Deprecated("Not OK")
|
||||||
|
actual class E actual constructor() {}
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user