[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
This commit is contained in:
sebastian.sellmair
2021-08-26 21:39:26 +02:00
committed by Space
parent fbfbb0c72c
commit 814a147a53
7 changed files with 300 additions and 18 deletions
@@ -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, "/")
@@ -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<CirClassOrTypeAliasType> {
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)
}
}
@@ -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<CirEntityId, BitWidth>) {
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<CirClassType> {
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)
}
}
@@ -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()
)
}
}
}
@@ -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<CirType> {
class TypeCommonizer(
private val classifiers: CirKnownClassifiers,
private val options: Options = Options.default
) : AssociativeCommonizer<CirType> {
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<CirTypeParameterType> {
@@ -35,11 +53,14 @@ private object TypeParameterTypeCommonizer : AssociativeCommonizer<CirTypeParame
}
}
private class FlexibleTypeAssociativeCommonizer(private val classifiers: CirKnownClassifiers) : AssociativeCommonizer<CirFlexibleType> {
private class FlexibleTypeAssociativeCommonizer(
private val classifiers: CirKnownClassifiers,
private val options: TypeCommonizer.Options
) : AssociativeCommonizer<CirFlexibleType> {
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,
@@ -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"
)
}
@@ -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()