From 8f4d527f18de67724b5f7ff67671d7bee9380c68 Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Sun, 11 Dec 2022 00:37:18 +0400 Subject: [PATCH] Self types: rewritten implementation of ControlFlowInfo from Kotlin compiler using Self types. Some parts were changed due to complexity of initial implementation. --- .../FirBlackBoxCodegenTestGenerated.java | 6 + .../box/fir/ControlFlowInfoSelfTypes.kt | 209 ++++++++++++++++++ .../codegen/BlackBoxCodegenTestGenerated.java | 6 + .../IrBlackBoxCodegenTestGenerated.java | 6 + .../LightAnalysisModeTestGenerated.java | 5 + .../js/test/JsCodegenBoxTestGenerated.java | 6 + .../test/ir/IrJsCodegenBoxTestGenerated.java | 6 + .../IrCodegenBoxWasmTestGenerated.java | 5 + .../NativeCodegenBoxTestGenerated.java | 6 + 9 files changed, 255 insertions(+) create mode 100644 compiler/testData/codegen/box/fir/ControlFlowInfoSelfTypes.kt diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java index 716dfd1a5f80f..8e70413aa7369 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java @@ -18223,6 +18223,12 @@ public void testConstValAccess() throws Exception { runTest("compiler/testData/codegen/box/fir/ConstValAccess.kt"); } + @Test + @TestMetadata("ControlFlowInfoSelfTypes.kt") + public void testControlFlowInfoSelfTypes() throws Exception { + runTest("compiler/testData/codegen/box/fir/ControlFlowInfoSelfTypes.kt"); + } + @Test @TestMetadata("CustomHashSetSize.kt") public void testCustomHashSetSize() throws Exception { diff --git a/compiler/testData/codegen/box/fir/ControlFlowInfoSelfTypes.kt b/compiler/testData/codegen/box/fir/ControlFlowInfoSelfTypes.kt new file mode 100644 index 0000000000000..99d3ec1ea104d --- /dev/null +++ b/compiler/testData/codegen/box/fir/ControlFlowInfoSelfTypes.kt @@ -0,0 +1,209 @@ +// TARGET_BACKEND: JVM_IR +// IGNORE_BACKEND: JVM_IR + +// WITH_STDLIB + +import kotlin.Self + +interface ReadOnlyControlFlowInfo { + fun getOrNull(key: K): D? + + // Only used in tests + fun asMap(): Map +} + +@Self +abstract class ControlFlowInfo +internal constructor( + protected val map: Map = mapOf() +) : Map by map, ReadOnlyControlFlowInfo { + protected abstract fun copy(newMap: Map): Self + + fun put(key: K, value: D): Self = + put(key, value, this[key] ?: null as D?) + + /** + * This overload exists just for sake of optimizations: in some cases we've just retrieved the old value, + * so we don't need to scan through the persistent hashmap again + */ + fun put(key: K, value: D, oldValue: D?): Self { + // Avoid a copy instance creation if new value is the same + if (value == oldValue) return this as Self + val newMap = map + (key to value) + return copy(newMap) + } + + override fun getOrNull(key: K): D? = this[key] ?: null as D? + override fun asMap() = this + + fun retainAll(predicate: (K) -> Boolean): Self { + val newMap = map.filter { predicate(it.key) } + return copy(newMap) + } + + override fun equals(other: Any?) = map == (other as? ControlFlowInfo<*, *, *>)?.map + + override fun hashCode() = map.hashCode() + + override fun toString() = map.toString() +} + +// ALIASES BEGIN + +typealias VariableDescriptor = String +typealias VariableUsageControlFlowInfo = ControlFlowInfo +typealias VariableUsageReadOnlyControlInfo = ReadOnlyControlFlowInfo + +// ALIASES END + + +// IMPLEMENTATIONS OF CONTROL FLOW INFOS BEGIN + +class UsageVariableControlFlowInfo(map: Map = mapOf()) : + VariableUsageControlFlowInfo(map), + VariableUsageReadOnlyControlInfo { + override fun copy(newMap: Map): UsageVariableControlFlowInfo = + UsageVariableControlFlowInfo(newMap) +} + +interface VariableInitReadOnlyControlFlowInfo : + ReadOnlyControlFlowInfo { + fun checkDefiniteInitializationInWhen(merge: VariableInitReadOnlyControlFlowInfo): Boolean +} + +class VariableInitControlFlowInfo(map: Map = mapOf()) : + VariableUsageControlFlowInfo(map), + VariableInitReadOnlyControlFlowInfo { + override fun copy(newMap: Map): VariableInitControlFlowInfo = + VariableInitControlFlowInfo(newMap) + + // this = output of EXHAUSTIVE_WHEN_ELSE instruction + // merge = input of MergeInstruction + // returns true if definite initialization in when happens here + override fun checkDefiniteInitializationInWhen(merge: VariableInitReadOnlyControlFlowInfo): Boolean { + for ((key, value) in iterator()) { + if (value.initState == InitState.INITIALIZED_EXHAUSTIVELY && + merge.getOrNull(key)?.initState == InitState.INITIALIZED + ) { + return true + } + } + return false + } +} + +// IMPLEMENTATIONS OF CONTROL FLOW INFOS END + +// STATES BEGIN + +class VariableControlFlowState private constructor(val initState: InitState, val isDeclared: Boolean) { + + fun definitelyInitialized(): Boolean = initState == InitState.INITIALIZED + + fun mayBeInitialized(): Boolean = initState != InitState.NOT_INITIALIZED + + override fun toString(): String { + if (initState == InitState.NOT_INITIALIZED && !isDeclared) return "-" + return "$initState${if (isDeclared) "D" else ""}" + } + + companion object { + + private val VS_IT = VariableControlFlowState(InitState.INITIALIZED, true) + private val VS_IF = VariableControlFlowState(InitState.INITIALIZED, false) + private val VS_ET = VariableControlFlowState(InitState.INITIALIZED_EXHAUSTIVELY, true) + private val VS_EF = VariableControlFlowState(InitState.INITIALIZED_EXHAUSTIVELY, false) + private val VS_UT = VariableControlFlowState(InitState.UNKNOWN, true) + private val VS_UF = VariableControlFlowState(InitState.UNKNOWN, false) + private val VS_NT = VariableControlFlowState(InitState.NOT_INITIALIZED, true) + private val VS_NF = VariableControlFlowState(InitState.NOT_INITIALIZED, false) + + fun create(initState: InitState, isDeclared: Boolean): VariableControlFlowState = + when (initState) { + InitState.INITIALIZED -> if (isDeclared) VS_IT else VS_IF + InitState.INITIALIZED_EXHAUSTIVELY -> if (isDeclared) VS_ET else VS_EF + InitState.UNKNOWN -> if (isDeclared) VS_UT else VS_UF + InitState.NOT_INITIALIZED -> if (isDeclared) VS_NT else VS_NF + } + + fun createInitializedExhaustively(isDeclared: Boolean): VariableControlFlowState = + create(InitState.INITIALIZED_EXHAUSTIVELY, isDeclared) + + fun create(isInitialized: Boolean, isDeclared: Boolean = false): VariableControlFlowState = + create(if (isInitialized) InitState.INITIALIZED else InitState.NOT_INITIALIZED, isDeclared) + + fun create(isDeclaredHere: Boolean, mergedEdgesData: VariableControlFlowState?): VariableControlFlowState = + create(true, isDeclaredHere || mergedEdgesData != null && mergedEdgesData.isDeclared) + } +} + +enum class VariableUseState(private val priority: Int) { + READ(3), + WRITTEN_AFTER_READ(2), + ONLY_WRITTEN_NEVER_READ(1), + UNUSED(0); + + fun merge(variableUseState: VariableUseState?): VariableUseState { + if (variableUseState == null || priority > variableUseState.priority) return this + return variableUseState + } + + companion object { + + @JvmStatic + fun isUsed(variableUseState: VariableUseState?): Boolean = + variableUseState != null && variableUseState != UNUSED + } +} + +enum class InitState(private val s: String) { + // Definitely initialized + INITIALIZED("I"), + // Fake initializer in else branch of "exhaustive when without else", see MagicKind.EXHAUSTIVE_WHEN_ELSE + INITIALIZED_EXHAUSTIVELY("IE"), + // Initialized in some branches, not initialized in other branches + UNKNOWN("I?"), + // Definitely not initialized + NOT_INITIALIZED(""); + + fun merge(other: InitState): InitState { + // X merge X = X + // X merge IE = IE merge X = X + // else X merge Y = I? + if (this == other || other == INITIALIZED_EXHAUSTIVELY) return this + if (this == INITIALIZED_EXHAUSTIVELY) return other + return UNKNOWN + } + + override fun toString() = s +} + +// STATES END + +fun box(): String { + val usageVariableControlFlowInfo: UsageVariableControlFlowInfo = UsageVariableControlFlowInfo( + mapOf( + "unused" to VariableUseState.UNUSED, + "read" to VariableUseState.READ + ) + ) + + val usageVariableControlFlowInfoUpdated: UsageVariableControlFlowInfo = + usageVariableControlFlowInfo.put("second unused", VariableUseState.UNUSED) + + + val variableInitControlFlowInfo: VariableInitControlFlowInfo = VariableInitControlFlowInfo( + mapOf( + "VS_IT" to VariableControlFlowState.create(InitState.INITIALIZED, isDeclared = true), + "VS_IF" to VariableControlFlowState.create(InitState.INITIALIZED, isDeclared = false) + ) + ) + + val updatedVariableInitControlFlowInfo = variableInitControlFlowInfo.put( + "VS_ET", VariableControlFlowState.create(InitState.INITIALIZED_EXHAUSTIVELY, isDeclared = true) + ) + + val predicate = usageVariableControlFlowInfoUpdated.containsKey("second unused") && updatedVariableInitControlFlowInfo.containsKey("VS_ET") + + return if (predicate) "OK" else "ERROR" +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java index e79df87a1064a..2f2deaf865ae1 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java @@ -17479,6 +17479,12 @@ public void testConstValAccess() throws Exception { runTest("compiler/testData/codegen/box/fir/ConstValAccess.kt"); } + @Test + @TestMetadata("ControlFlowInfoSelfTypes.kt") + public void testControlFlowInfoSelfTypes() throws Exception { + runTest("compiler/testData/codegen/box/fir/ControlFlowInfoSelfTypes.kt"); + } + @Test @TestMetadata("differentSinceKotlin.kt") public void testDifferentSinceKotlin() throws Exception { diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java index f01dea9da06f5..5a0b7db47fb82 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java @@ -18223,6 +18223,12 @@ public void testConstValAccess() throws Exception { runTest("compiler/testData/codegen/box/fir/ConstValAccess.kt"); } + @Test + @TestMetadata("ControlFlowInfoSelfTypes.kt") + public void testControlFlowInfoSelfTypes() throws Exception { + runTest("compiler/testData/codegen/box/fir/ControlFlowInfoSelfTypes.kt"); + } + @Test @TestMetadata("CustomHashSetSize.kt") public void testCustomHashSetSize() throws Exception { diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 666be14d5f3f4..1daf43c174fe4 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -14488,6 +14488,11 @@ public void testConstValAccess() throws Exception { runTest("compiler/testData/codegen/box/fir/ConstValAccess.kt"); } + @TestMetadata("ControlFlowInfoSelfTypes.kt") + public void testControlFlowInfoSelfTypes() throws Exception { + runTest("compiler/testData/codegen/box/fir/ControlFlowInfoSelfTypes.kt"); + } + @TestMetadata("differentSinceKotlin.kt") public void testDifferentSinceKotlin() throws Exception { runTest("compiler/testData/codegen/box/fir/differentSinceKotlin.kt"); diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/JsCodegenBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/JsCodegenBoxTestGenerated.java index 74a37e8138843..93e75bf50b33a 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/JsCodegenBoxTestGenerated.java @@ -13509,6 +13509,12 @@ public void testClassCanNotBeCastedToVoid() throws Exception { runTest("compiler/testData/codegen/box/fir/classCanNotBeCastedToVoid.kt"); } + @Test + @TestMetadata("ControlFlowInfoSelfTypes.kt") + public void testControlFlowInfoSelfTypes() throws Exception { + runTest("compiler/testData/codegen/box/fir/ControlFlowInfoSelfTypes.kt"); + } + @Test @TestMetadata("falsePositiveBoundSmartcast.kt") public void testFalsePositiveBoundSmartcast() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsCodegenBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsCodegenBoxTestGenerated.java index f817699886e19..6e99eb17bc339 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsCodegenBoxTestGenerated.java @@ -13605,6 +13605,12 @@ public void testClassCanNotBeCastedToVoid() throws Exception { runTest("compiler/testData/codegen/box/fir/classCanNotBeCastedToVoid.kt"); } + @Test + @TestMetadata("ControlFlowInfoSelfTypes.kt") + public void testControlFlowInfoSelfTypes() throws Exception { + runTest("compiler/testData/codegen/box/fir/ControlFlowInfoSelfTypes.kt"); + } + @Test @TestMetadata("falsePositiveBoundSmartcast.kt") public void testFalsePositiveBoundSmartcast() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/testOld/wasm/semantics/IrCodegenBoxWasmTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/testOld/wasm/semantics/IrCodegenBoxWasmTestGenerated.java index 6d16196e87bd5..a1931054699e3 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/testOld/wasm/semantics/IrCodegenBoxWasmTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/testOld/wasm/semantics/IrCodegenBoxWasmTestGenerated.java @@ -12083,6 +12083,11 @@ public void testClassCanNotBeCastedToVoid() throws Exception { runTest("compiler/testData/codegen/box/fir/classCanNotBeCastedToVoid.kt"); } + @TestMetadata("ControlFlowInfoSelfTypes.kt") + public void testControlFlowInfoSelfTypes() throws Exception { + runTest("compiler/testData/codegen/box/fir/ControlFlowInfoSelfTypes.kt"); + } + @TestMetadata("falsePositiveBoundSmartcast.kt") public void testFalsePositiveBoundSmartcast() throws Exception { runTest("compiler/testData/codegen/box/fir/falsePositiveBoundSmartcast.kt"); diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeCodegenBoxTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeCodegenBoxTestGenerated.java index 21b2f65e3a336..85253d9570a44 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeCodegenBoxTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeCodegenBoxTestGenerated.java @@ -14641,6 +14641,12 @@ public void testClassCanNotBeCastedToVoid() throws Exception { runTest("compiler/testData/codegen/box/fir/classCanNotBeCastedToVoid.kt"); } + @Test + @TestMetadata("ControlFlowInfoSelfTypes.kt") + public void testControlFlowInfoSelfTypes() throws Exception { + runTest("compiler/testData/codegen/box/fir/ControlFlowInfoSelfTypes.kt"); + } + @Test @TestMetadata("falsePositiveBoundSmartcast.kt") public void testFalsePositiveBoundSmartcast() throws Exception {