From 814a147a530f5620b55084724836ce0d84c89737 Mon Sep 17 00:00:00 2001 From: "sebastian.sellmair" Date: Thu, 26 Aug 2021 21:39:26 +0200 Subject: [PATCH] [Commonizer] Implement optimistic number commonization for typealias When typealias point to a number of different bit width on certain platforms, this implementation will choose the number type with higher bit width as commonization result for typealias types and typealias underlying types. This will effectively port the "analyse code for a single selected platform" UX approach into HMPP (for numbers only) ^KT-48455 Verification Pending --- .../kotlin/commonizer/cir/CirName.kt | 5 +- .../core/ClassOrTypeAliasTypeCommonizer.kt | 9 +- .../core/OptimisticNumbersTypeCommonizer.kt | 80 +++++++++ .../commonizer/core/TypeAliasCommonizer.kt | 7 +- .../kotlin/commonizer/core/TypeCommonizer.kt | 33 +++- .../AliasedNumberTypeCommonizationTest.kt | 165 ++++++++++++++++++ ...hicalClassAndTypeAliasCommonizationTest.kt | 19 +- 7 files changed, 300 insertions(+), 18 deletions(-) create mode 100644 native/commonizer/src/org/jetbrains/kotlin/commonizer/core/OptimisticNumbersTypeCommonizer.kt create mode 100644 native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/AliasedNumberTypeCommonizationTest.kt diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/cir/CirName.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/cir/CirName.kt index f7b0effaf1b..d29793e3c48 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/cir/CirName.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/cir/CirName.kt @@ -95,7 +95,10 @@ class CirEntityId private constructor(val packageName: CirPackageName, val relat else -> false } - override fun hashCode(): Int = hashCode(packageName).appendHashCode(relativeNameSegments) + private var _hashCode: Int = 0 + + override fun hashCode(): Int = if (_hashCode != 0) _hashCode else + hashCode(packageName).appendHashCode(relativeNameSegments).also { hashCode -> _hashCode = hashCode } override fun toString(): String = buildString { packageName.segments.joinTo(this, "/") diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassOrTypeAliasTypeCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassOrTypeAliasTypeCommonizer.kt index 6c3787fe45e..4ad239c243c 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassOrTypeAliasTypeCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassOrTypeAliasTypeCommonizer.kt @@ -11,12 +11,14 @@ import org.jetbrains.kotlin.commonizer.cir.CirTypeAliasType import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers internal class ClassOrTypeAliasTypeCommonizer( - private val classifiers: CirKnownClassifiers + private val classifiers: CirKnownClassifiers, + private val options: TypeCommonizer.Options ) : AssociativeCommonizer { override fun commonize(first: CirClassOrTypeAliasType, second: CirClassOrTypeAliasType): CirClassOrTypeAliasType? { if (first is CirClassType && second is CirClassType) { return ClassTypeCommonizer(classifiers).commonize(listOf(first, second)) + ?: if (options.allowOptimisticNumberTypeCommonization) OptimisticNumbersTypeCommonizer.commonize(first, second) else null } if (first is CirTypeAliasType && second is CirTypeAliasType) { @@ -25,7 +27,8 @@ internal class ClassOrTypeAliasTypeCommonizer( try our luck with commonizing those class types */ return TypeAliasTypeCommonizer(classifiers).commonize(listOf(first, second)) - ?: ClassTypeCommonizer(classifiers).commonize(listOf(first.expandedType(), second.expandedType())) + ?: ClassOrTypeAliasTypeCommonizer(classifiers, options.withAllowOptimisticNumberTypeCommonization()) + .commonize(first.expandedType(), second.expandedType()) } val classType = when { @@ -47,7 +50,7 @@ internal class ClassOrTypeAliasTypeCommonizer( val typeAliasClassType = TypeAliasTypeCommonizer(classifiers).commonize(listOf(typeAliasType))?.expandedType() ?: typeAliasType.expandedType() - return ClassTypeCommonizer(classifiers).commonize(listOf(classType, typeAliasClassType)) + return commonize(classType, typeAliasClassType) } } diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/OptimisticNumbersTypeCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/OptimisticNumbersTypeCommonizer.kt new file mode 100644 index 00000000000..785da516c3b --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/OptimisticNumbersTypeCommonizer.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.commonizer.core + +import org.jetbrains.kotlin.commonizer.cir.CirClassType +import org.jetbrains.kotlin.commonizer.cir.CirEntityId + +private typealias BitWidth = Int + +private class SubstitutableNumbers(private val numbers: Map) { + fun choose(first: CirClassType, second: CirClassType): CirClassType? { + val firstBitWidth = numbers[first.classifierId] ?: return null + val secondBitWidth = numbers[second.classifierId] ?: return null + return if (secondBitWidth > firstBitWidth) second else first + } +} + +private val signedIntegers = SubstitutableNumbers( + mapOf( + CirEntityId.create("kotlin/Byte") to 8, + CirEntityId.create("kotlin/Short") to 16, + CirEntityId.create("kotlin/Int") to 32, + CirEntityId.create("kotlin/Long") to 64 + ) +) + +private val unsignedIntegers = SubstitutableNumbers( + mapOf( + CirEntityId.create("kotlin/UByte") to 8, + CirEntityId.create("kotlin/UShort") to 16, + CirEntityId.create("kotlin/UInt") to 32, + CirEntityId.create("kotlin/ULong") to 64 + ) +) + +private val floatingPoints = SubstitutableNumbers( + mapOf( + CirEntityId.create("kotlin/Float") to 32, + CirEntityId.create("kotlin/Double") to 64, + ) +) + +private val signedVarIntegers = SubstitutableNumbers( + mapOf( + CirEntityId.create("kotlinx/cinterop/ByteVarOf") to 8, + CirEntityId.create("kotlinx/cinterop/ShortVarOf") to 16, + CirEntityId.create("kotlinx/cinterop/IntVarOf") to 32, + CirEntityId.create("kotlinx/cinterop/LongVarOf") to 64, + ) +) + +private val unsignedVarIntegers = SubstitutableNumbers( + mapOf( + CirEntityId.create("kotlinx/cinterop/UByteVarOf") to 8, + CirEntityId.create("kotlinx/cinterop/UShortVarOf") to 16, + CirEntityId.create("kotlinx/cinterop/UIntVarOf") to 32, + CirEntityId.create("kotlinx/cinterop/ULongVarOf") to 64, + ) +) + +private val floatingPointVars = SubstitutableNumbers( + mapOf( + CirEntityId.create("kotlinx/cinterop/FloatVarOf") to 32, + CirEntityId.create("kotlinx/cinterop/DoubleVarOf") to 64, + ) +) + +object OptimisticNumbersTypeCommonizer : AssociativeCommonizer { + override fun commonize(first: CirClassType, second: CirClassType): CirClassType? { + return signedIntegers.choose(first, second) + ?: unsignedIntegers.choose(first, second) + ?: floatingPoints.choose(first, second) + ?: signedVarIntegers.choose(first, second) + ?: unsignedVarIntegers.choose(first, second) + ?: floatingPointVars.choose(first, second) + } +} \ No newline at end of file diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasCommonizer.kt index 9e96026272d..1589e68d54e 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasCommonizer.kt @@ -5,7 +5,8 @@ package org.jetbrains.kotlin.commonizer.core -import org.jetbrains.kotlin.commonizer.cir.* +import org.jetbrains.kotlin.commonizer.cir.CirClassOrTypeAliasType +import org.jetbrains.kotlin.commonizer.cir.CirTypeAlias import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers class TypeAliasCommonizer( @@ -18,7 +19,7 @@ class TypeAliasCommonizer( val typeParameters = TypeParameterListCommonizer(classifiers) .commonize(listOf(first.typeParameters, second.typeParameters)) ?: return null - val underlyingType = TypeCommonizer(classifiers) + val underlyingType = TypeCommonizer(classifiers, TypeCommonizer.Options.default.withAllowOptimisticNumberTypeCommonization()) .commonize(first.underlyingType, second.underlyingType) as? CirClassOrTypeAliasType ?: return null val visibility = VisibilityCommonizer.lowering().commonize(listOf(first, second)) ?: return null @@ -32,4 +33,4 @@ class TypeAliasCommonizer( expandedType = underlyingType.expandedType() ) } -} \ No newline at end of file +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeCommonizer.kt index 36465234958..bd6a2fa4457 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeCommonizer.kt @@ -8,10 +8,14 @@ package org.jetbrains.kotlin.commonizer.core import org.jetbrains.kotlin.commonizer.cir.* import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers -class TypeCommonizer(private val classifiers: CirKnownClassifiers) : AssociativeCommonizer { + +class TypeCommonizer( + private val classifiers: CirKnownClassifiers, + private val options: Options = Options.default +) : AssociativeCommonizer { override fun commonize(first: CirType, second: CirType): CirType? { if (first is CirClassOrTypeAliasType && second is CirClassOrTypeAliasType) { - return ClassOrTypeAliasTypeCommonizer(classifiers).commonize(first, second) + return ClassOrTypeAliasTypeCommonizer(classifiers, options).commonize(first, second) } if (first is CirTypeParameterType && second is CirTypeParameterType) { @@ -19,11 +23,25 @@ class TypeCommonizer(private val classifiers: CirKnownClassifiers) : Associative } if (first is CirFlexibleType && second is CirFlexibleType) { - return FlexibleTypeAssociativeCommonizer(classifiers).commonize(first, second) + return FlexibleTypeAssociativeCommonizer(classifiers, options).commonize(first, second) } return null } + + data class Options( + val allowOptimisticNumberTypeCommonization: Boolean = false + ) { + + fun withAllowOptimisticNumberTypeCommonization(): Options { + return if (allowOptimisticNumberTypeCommonization) this + else copy(allowOptimisticNumberTypeCommonization = true) + } + + companion object { + val default = Options() + } + } } private object TypeParameterTypeCommonizer : AssociativeCommonizer { @@ -35,11 +53,14 @@ private object TypeParameterTypeCommonizer : AssociativeCommonizer { +private class FlexibleTypeAssociativeCommonizer( + private val classifiers: CirKnownClassifiers, + private val options: TypeCommonizer.Options +) : AssociativeCommonizer { override fun commonize(first: CirFlexibleType, second: CirFlexibleType): CirFlexibleType? { - val lowerBound = TypeCommonizer(classifiers).commonize(first.lowerBound, second.lowerBound) ?: return null - val upperBound = TypeCommonizer(classifiers).commonize(first.upperBound, second.upperBound) ?: return null + val lowerBound = TypeCommonizer(classifiers, options).commonize(first.lowerBound, second.lowerBound) ?: return null + val upperBound = TypeCommonizer(classifiers, options).commonize(first.upperBound, second.upperBound) ?: return null return CirFlexibleType( lowerBound = lowerBound as CirSimpleType, diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/AliasedNumberTypeCommonizationTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/AliasedNumberTypeCommonizationTest.kt new file mode 100644 index 00000000000..efd593e2494 --- /dev/null +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/AliasedNumberTypeCommonizationTest.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.commonizer.hierarchical + +import org.jetbrains.kotlin.commonizer.AbstractInlineSourcesCommonizationTest +import org.jetbrains.kotlin.commonizer.assertCommonized +import org.jetbrains.kotlin.commonizer.utils.InlineSourceBuilder + +class AliasedNumberTypeCommonizationTest : AbstractInlineSourcesCommonizationTest() { + + fun `test Int and Long - typealias`() { + val result = commonize { + outputTarget("(a, b)") + simpleSingleSourceTarget("a", "typealias X = Int") + simpleSingleSourceTarget("b", "typealias X = Long") + } + + result.assertCommonized("(a, b)", "typealias X = Long") + } + + fun `test UInt and ULong - typealias`() { + val result = commonize { + outputTarget("(a, b)") + registerDependency("a", "b", "(a, b)") { unsignedIntegers() } + simpleSingleSourceTarget("a", "typealias X = UInt") + simpleSingleSourceTarget("b", "typealias X = ULong") + } + + result.assertCommonized("(a, b)", "typealias X = ULong") + } + + + fun `test int and long - chain - typealias`() { + val result = commonize { + outputTarget("(a, b)") + + simpleSingleSourceTarget( + "a", """ + typealias A = Int + typealias B = A + typealias C = B + typealias X = C + """.trimIndent() + ) + + simpleSingleSourceTarget( + "b", """ + typealias A = Long + typealias B = A + typealias X = B + """.trimIndent() + ) + } + + result.assertCommonized( + "(a, b)", """ + typealias A = Long + typealias B = A + typealias X = Long + """.trimIndent() + ) + } + + fun `test function with pure number types parameter`() { + val result = commonize { + outputTarget("(a, b)") + simpleSingleSourceTarget("a", "fun x(p: Int)") + simpleSingleSourceTarget("b", "fun x(p: Long)") + } + + /* + Only functions that use a TA in their signature are supposed to be + commonized using our number's commonization hack. + + This is a hard requirement. It would also be reasonable if we would add + support for this case, since there would be reasonable code that people + could write with this! + */ + result.assertCommonized("(a, b)", "") + } + + fun `test function with aliased number value parameter`() { + val result = commonize { + outputTarget("(a, b)") + simpleSingleSourceTarget( + "a", """ + typealias A = Int + typealias X = A + fun x(p: X) + """.trimIndent() + ) + simpleSingleSourceTarget( + "b", """ + typealias B = Long + typealias X = B + fun x(p: X) + """.trimIndent() + ) + } + + result.assertCommonized( + "(a, b)", """ + typealias X = Long + expect fun x(p: X) + """.trimIndent() + ) + } + + fun `test property with pure number return type`() { + val result = commonize { + outputTarget("(a, b)") + registerDependency("a", "b", "(a, b)") { unsignedIntegers() } + simpleSingleSourceTarget("a", "val x: UInt") + simpleSingleSourceTarget("b", "val x: ULong") + } + + /* + Only commonize return types that were specified using a type-alias. + As with function value parameters, this is not a hard requirement. + It would also be reasonable to support this case. + */ + result.assertCommonized("(a, b)", "") + } + + fun `test property with aliased number return type`() { + val result = commonize { + outputTarget("(a, b)") + registerDependency("a", "b", "(a, b)") { unsignedIntegers() } + simpleSingleSourceTarget( + "a", """ + typealias X = UShort + val x: X = TODO() + """.trimIndent() + ) + simpleSingleSourceTarget( + "b", """ + typealias X = ULong + val x: X = TODO() + """.trimIndent() + ) + } + + result.assertCommonized( + "(a, b)", """ + typealias X = ULong + expect val x: X + """.trimIndent() + ) + } +} + +private fun InlineSourceBuilder.ModuleBuilder.unsignedIntegers() { + source( + """ + package kotlin + class UByte + class UShort + class UInt + class ULong + """.trimIndent(), "unsigned.kt" + ) +} \ No newline at end of file diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalClassAndTypeAliasCommonizationTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalClassAndTypeAliasCommonizationTest.kt index d7b0ca7511c..9d37157a9c2 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalClassAndTypeAliasCommonizationTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalClassAndTypeAliasCommonizationTest.kt @@ -33,13 +33,22 @@ class HierarchicalClassAndTypeAliasCommonizationTest : AbstractInlineSourcesComm fun `test commonization of typeAlias and class hierarchically`() { val result = commonize { outputTarget("(a, b)", "(c, d)", "(a, b, c, d)") - simpleSingleSourceTarget("a", "typealias X = Int") - simpleSingleSourceTarget("b", "typealias X = Long") + registerDependency("a", "b", "c", "d", "(a, b)", "(c, d)", "(a, b, c, d)") { + source( + """ + interface A + interface B: A + interface C: A + """.trimIndent() + ) + } + simpleSingleSourceTarget("a", "typealias X = B") + simpleSingleSourceTarget("b", "typealias X = C") simpleSingleSourceTarget("c", "class X") simpleSingleSourceTarget("d", "typealias X = Short") } - result.assertCommonized("(a, b)", "expect class X: Number") + result.assertCommonized("(a, b)", "expect class X: A") result.assertCommonized("(c, d)", "expect class X") result.assertCommonized("((a, b), (c, d))", "expect class X") } @@ -558,7 +567,7 @@ class HierarchicalClassAndTypeAliasCommonizationTest : AbstractInlineSourcesComm result.assertCommonized( "(c, d)", """ - expect class Proxy: Number + typealias Proxy = Int typealias X = Proxy expect val x: X """.trimIndent() @@ -566,7 +575,7 @@ class HierarchicalClassAndTypeAliasCommonizationTest : AbstractInlineSourcesComm result.assertCommonized( "(a, b, c, d)", """ - expect class Proxy: Number + typealias Proxy = Long typealias X = Proxy expect val x: X """.trimIndent()