[IrActualizer] Update doc on how the actualization process works.

This commit is contained in:
Pavel Kunyavskiy
2023-12-15 15:03:26 +01:00
committed by Space Team
parent 7813bb35cf
commit f50fab65cc
2 changed files with 92 additions and 43 deletions
@@ -78,11 +78,11 @@ class IrActualizer(
val expectActualMap = collector.collect(classActualizationInfo)
if (!useIrFakeOverrideBuilder) {
// 3. Actualize expect fake overrides in non-expect classes inside common or multi-platform module.
// 2. Actualize expect fake overrides in non-expect classes inside common or multi-platform module.
// It's probably important to run FakeOverridesActualizer before ActualFakeOverridesAdder
FakeOverridesActualizer(expectActualMap).apply { dependentFragments.forEach { visitModuleFragment(it) } }
// 4. Add fake overrides to non-expect classes inside common or multi-platform module,
// 3. Add fake overrides to non-expect classes inside common or multi-platform module,
// taken from these non-expect classes actualized super classes.
ActualFakeOverridesAdder(
expectActualMap,
@@ -91,16 +91,16 @@ class IrActualizer(
).apply { dependentFragments.forEach { visitModuleFragment(it) } }
}
// 5. Copy and actualize function parameter default values from expect functions
// 4. Copy and actualize function parameter default values from expect functions
val symbolRemapper = ActualizerSymbolRemapper(expectActualMap)
val typeRemapper = DeepCopyTypeRemapper(symbolRemapper)
FunctionDefaultParametersActualizer(symbolRemapper, typeRemapper, expectActualMap).actualize()
// 6. Actualize expect calls in dependent fragments using info obtained in the previous steps
// 5. Actualize expect calls in dependent fragments using info obtained in the previous steps
val actualizerVisitor = ActualizerVisitor(symbolRemapper, typeRemapper)
dependentFragments.forEach { it.transform(actualizerVisitor, null) }
// 8. Move all declarations to mainFragment
// 6. Move all declarations to mainFragment
mergeIrFragments(mainFragment, dependentFragments)
return expectActualMap
}
+87 -38
View File
@@ -10,8 +10,9 @@ This document describes the implementation of the KMP support in the K2 Compiler
- [Metadata Compilation](#metadata-compilation)
- [Platform Compilation](#platform-compilation)
- [FIR2IR](#fir2ir)
- [IRActualizer](#iractualizer)
- [Fake overrides](#fake-overrides)
- [Lazy classes](#lazy-classes)
- [IRActualizer](#iractualizer)
- [Frontend support for expect/actual](#frontend-support-for-expectactual)
- [Type refinement](#type-refinement)
- [Default propagation](#default-propagation)
@@ -256,46 +257,12 @@ The same applies to the declarations from [binary dependencies](#platform-compil
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.
Also, the following example shows that it is impossible to build valid fake overrides during common
module compilation.
Ex:
```kotlin
@@ -328,7 +295,89 @@ class Impl : K {
```
In the given example during the actualization, we need to combine (1) and (2) into one fake-override.
***Note: Approach described below is a target. It's not yet enabled by default. See KT-61514 for details.***
In the given example during the actualization, common compilation would produce (1) and (2), which need to be combined
into one fake override.
There is a new scheme, which is enabled by -Xuse-ir-fake-override-builder flag, and should become default at some point.
In it, [Fir2Ir](#fir2ir) creates a special symbol for fake overrides calls, and no declarations for fake overrides
(except one in [Lazy classes](#lazy-classes)). A special symbol, represented by [org.jetbrains.kotlin.ir.symbols.impl.IrFakeOverrideSymbolBase](../../compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/symbols/impl/IrFakeOverrideSymbol.kt) and its inheritors,
is effectively a pair of real declaration symbol and dispatch receiver class. This symbol can't be bound,
so backend can't work with it. To fix it, there is a phase, which replaces them with normal ones
after [actualization](#actualization). The symbol is mapped to single one overriding corresponding
real symbol within corresponding dispatch receiver class. It can be both a fake override and real declared symbol.
In theory, doing the same with other synthetic declarations (delegated members, data class generated members, etc)
can lead to more consistent behaviour, but we don't do it know, as we are not aware of any problems
### Lazy classes
Lazy classes represent classes used from compiled sources, but not present in them. This can be classes
from dependencies, java sources, or sources already compiled on previous round of incremental compilation.
Lazy classes are a special case for fake override building. They are now handled with frontend builder.
It can be done correctly, as they can only exist in platform session.
There is a technical issue with them. It's possible to have a lazy class with normal class super-type,
so it can refer to a fake override symbol in overridden symbols of some methods.
But we can't fix them all in the same place, as normal ones, as it would trigger lazy computations, which should be avoided.
To fix this, the rebuild is delayed while possible, using [org.jetbrains.kotlin.fir.backend.Fir2IrSymbolsMappingForLazyClasses](../../compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrSymbolsMappingForLazyClasses.kt),
which stores delayed operations to happen, when some read lazy property.
### 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.
- IR can contain unbound fake override symbols
**Outputs:**
- Single IR module.
- No expect declarations in the IR.
- No unbound symbols 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 for classifiers is constructed and applied
- Fake overrides are constructed
- Expect -> actual binding for classifiers is callables and applied
- 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
- 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 symbols are replaced with normal ones
- Constants are evaluated
- 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/)
- Errors can be emitted if some expect-actual matches are bad.
There are several restrictions, because of which this order of actions is required:
- Fake overrides can't be built before classes are actualized
- Because we need to know what is real supertype.
- Callables can't be actulaized before fake overrides are built
- Some of them can match with fake override
- Constants can't be evaluated before callables are actualized
- As some expect function can become possible to evaluate
- Checkers can't run before constants are evaluated
- They need to check of some value are equal
## Frontend support for expect/actual