backend: improve value types support:
Introduce value type definition. Use the same type checking strategy in codegen and autoboxing. Optimize the latter by that.
This commit is contained in:
committed by
SvyatoslavScherbina
parent
31a0423225
commit
15107f4cdc
+68
@@ -0,0 +1,68 @@
|
|||||||
|
package org.jetbrains.kotlin.backend.konan
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||||
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value type is a kind of Kotlin types represented as plain value in generated code, not as an object reference.
|
||||||
|
* Such types may require autoboxing.
|
||||||
|
*
|
||||||
|
* The value type nearly corresponds to `[classFqName]` or `[classFqName]?` Kotlin type (depending on [isNullable]).
|
||||||
|
*
|
||||||
|
* @property classFqName name of the base value type class
|
||||||
|
* @property isNullable whether `null` is included into this value type
|
||||||
|
*/
|
||||||
|
enum class ValueType(val classFqName: FqNameUnsafe, val isNullable: Boolean = false) {
|
||||||
|
BOOLEAN(KotlinBuiltIns.FQ_NAMES._boolean),
|
||||||
|
CHAR(KotlinBuiltIns.FQ_NAMES._char),
|
||||||
|
|
||||||
|
BYTE(KotlinBuiltIns.FQ_NAMES._byte),
|
||||||
|
SHORT(KotlinBuiltIns.FQ_NAMES._short),
|
||||||
|
INT(KotlinBuiltIns.FQ_NAMES._int),
|
||||||
|
LONG(KotlinBuiltIns.FQ_NAMES._long),
|
||||||
|
FLOAT(KotlinBuiltIns.FQ_NAMES._float),
|
||||||
|
DOUBLE(KotlinBuiltIns.FQ_NAMES._double),
|
||||||
|
|
||||||
|
UNBOUND_CALLABLE_REFERENCE(FqNameUnsafe("konan.internal.UnboundCallableReference"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KotlinType.isConstructedFromGivenClass(fqName: FqNameUnsafe) =
|
||||||
|
KotlinBuiltIns.isConstructedFromGivenClass(this, fqName)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return `true` if this type must be represented as given value type in generated code.
|
||||||
|
*/
|
||||||
|
tailrec fun KotlinType.isRepresentedAs(valueType: ValueType): Boolean {
|
||||||
|
if (this.isMarkedNullable && !valueType.isNullable) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isConstructedFromGivenClass(valueType.classFqName)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supertypes should be checked even for "final" value types (e.g. Int)
|
||||||
|
// to treat type parameter `T` with `Int` upper bound as value type.
|
||||||
|
// This behavior is observed on Kotlin JVM and used in interop implementation.
|
||||||
|
//
|
||||||
|
// However to optimize this method only first supertype is checked
|
||||||
|
// (it is supposed to be enough in all sane cases).
|
||||||
|
val firstSupertype = this.constructor.supertypes.firstOrNull() ?: return false
|
||||||
|
return firstSupertype.isRepresentedAs(valueType)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return `true` if this type without `null` value must be represented as given value type in generated code.
|
||||||
|
*
|
||||||
|
* TODO: this method can be considered as a hack; rework its usages.
|
||||||
|
*/
|
||||||
|
tailrec fun KotlinType.notNullableIsRepresentedAs(valueType: ValueType): Boolean {
|
||||||
|
if (this.isConstructedFromGivenClass(valueType.classFqName)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// See comment in [isRepresentedAs].
|
||||||
|
val firstSupertype = this.constructor.supertypes.firstOrNull() ?: return false
|
||||||
|
return firstSupertype.notNullableIsRepresentedAs(valueType)
|
||||||
|
}
|
||||||
+3
-14
@@ -1,6 +1,8 @@
|
|||||||
package org.jetbrains.kotlin.backend.konan.descriptors
|
package org.jetbrains.kotlin.backend.konan.descriptors
|
||||||
|
|
||||||
import org.jetbrains.kotlin.backend.konan.KonanBuiltIns
|
import org.jetbrains.kotlin.backend.konan.KonanBuiltIns
|
||||||
|
import org.jetbrains.kotlin.backend.konan.ValueType
|
||||||
|
import org.jetbrains.kotlin.backend.konan.isRepresentedAs
|
||||||
import org.jetbrains.kotlin.backend.konan.llvm.functionName
|
import org.jetbrains.kotlin.backend.konan.llvm.functionName
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.builtins.isFunctionType
|
import org.jetbrains.kotlin.builtins.isFunctionType
|
||||||
@@ -118,20 +120,7 @@ internal fun KonanBuiltIns.getKonanInternalFunctions(name: String): List<Functio
|
|||||||
return konanInternal.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND).toList()
|
return konanInternal.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND).toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val UNBOUND_CALLABLE_REFERENCE = "UnboundCallableReference"
|
internal fun KotlinType.isUnboundCallableReference() = this.isRepresentedAs(ValueType.UNBOUND_CALLABLE_REFERENCE)
|
||||||
|
|
||||||
/**
|
|
||||||
* `konan.internal.UnboundCallableReference` type or `null` if it is not available.
|
|
||||||
*/
|
|
||||||
internal val KonanBuiltIns.unboundCallableReferenceTypeOrNull: KotlinType?
|
|
||||||
get() = this.getKonanInternalClassOrNull(UNBOUND_CALLABLE_REFERENCE)?.defaultType
|
|
||||||
|
|
||||||
internal fun KotlinType.isUnboundCallableReference(): Boolean {
|
|
||||||
val classDescriptor = TypeUtils.getClassDescriptor(this) ?: return false
|
|
||||||
|
|
||||||
return !this.isMarkedNullable &&
|
|
||||||
classDescriptor.fqNameUnsafe.asString() == "konan.internal.$UNBOUND_CALLABLE_REFERENCE"
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val CallableDescriptor.allValueParameters: List<ParameterDescriptor>
|
internal val CallableDescriptor.allValueParameters: List<ParameterDescriptor>
|
||||||
get() {
|
get() {
|
||||||
|
|||||||
+22
-16
@@ -1,28 +1,34 @@
|
|||||||
package org.jetbrains.kotlin.backend.konan.llvm
|
package org.jetbrains.kotlin.backend.konan.llvm
|
||||||
|
|
||||||
import llvm.*
|
import llvm.*
|
||||||
import org.jetbrains.kotlin.backend.konan.descriptors.isUnboundCallableReference
|
import org.jetbrains.kotlin.backend.konan.ValueType
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.backend.konan.isRepresentedAs
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||||
|
|
||||||
internal fun RuntimeAware.getLLVMType(type: KotlinType): LLVMTypeRef {
|
private val valueTypes = ValueType.values().associate {
|
||||||
return when {
|
it to when (it) {
|
||||||
// Nullable types must be represented as objects for boxing.
|
ValueType.BOOLEAN -> LLVMInt1Type()
|
||||||
type.isMarkedNullable -> this.kObjHeaderPtr
|
ValueType.BYTE -> LLVMInt8Type()
|
||||||
type.isUnboundCallableReference() -> int8TypePtr
|
ValueType.SHORT, ValueType.CHAR -> LLVMInt16Type()
|
||||||
KotlinBuiltIns.isBoolean(type) -> LLVMInt1Type()
|
ValueType.INT -> LLVMInt32Type()
|
||||||
KotlinBuiltIns.isByte(type) -> LLVMInt8Type()
|
ValueType.LONG -> LLVMInt64Type()
|
||||||
KotlinBuiltIns.isShort(type) || KotlinBuiltIns.isChar(type) -> LLVMInt16Type()
|
ValueType.FLOAT -> LLVMFloatType()
|
||||||
KotlinBuiltIns.isInt(type) -> LLVMInt32Type()
|
ValueType.DOUBLE -> LLVMDoubleType()
|
||||||
KotlinBuiltIns.isLong(type) -> LLVMInt64Type()
|
ValueType.UNBOUND_CALLABLE_REFERENCE -> int8TypePtr
|
||||||
KotlinBuiltIns.isFloat(type) -> LLVMFloatType()
|
|
||||||
KotlinBuiltIns.isDouble(type) -> LLVMDoubleType()
|
|
||||||
!KotlinBuiltIns.isPrimitiveType(type) -> this.kObjHeaderPtr
|
|
||||||
else -> throw NotImplementedError(type.toString() + " is not supported")
|
|
||||||
}!!
|
}!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun RuntimeAware.getLLVMType(type: KotlinType): LLVMTypeRef {
|
||||||
|
for ((valueType, llvmType) in valueTypes) {
|
||||||
|
if (type.isRepresentedAs(valueType)) {
|
||||||
|
return llvmType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.kObjHeaderPtr
|
||||||
|
}
|
||||||
|
|
||||||
internal fun RuntimeAware.getLLVMReturnType(type: KotlinType): LLVMTypeRef {
|
internal fun RuntimeAware.getLLVMReturnType(type: KotlinType): LLVMTypeRef {
|
||||||
return when {
|
return when {
|
||||||
type.isUnit() -> LLVMVoidType()!!
|
type.isUnit() -> LLVMVoidType()!!
|
||||||
|
|||||||
+29
-44
@@ -3,9 +3,11 @@ package org.jetbrains.kotlin.backend.konan.lower
|
|||||||
import org.jetbrains.kotlin.backend.common.AbstractValueUsageTransformer
|
import org.jetbrains.kotlin.backend.common.AbstractValueUsageTransformer
|
||||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||||
import org.jetbrains.kotlin.backend.konan.Context
|
import org.jetbrains.kotlin.backend.konan.Context
|
||||||
|
import org.jetbrains.kotlin.backend.konan.ValueType
|
||||||
import org.jetbrains.kotlin.backend.konan.descriptors.getKonanInternalClass
|
import org.jetbrains.kotlin.backend.konan.descriptors.getKonanInternalClass
|
||||||
import org.jetbrains.kotlin.backend.konan.descriptors.getKonanInternalFunctions
|
import org.jetbrains.kotlin.backend.konan.descriptors.getKonanInternalFunctions
|
||||||
import org.jetbrains.kotlin.backend.konan.descriptors.unboundCallableReferenceTypeOrNull
|
import org.jetbrains.kotlin.backend.konan.notNullableIsRepresentedAs
|
||||||
|
import org.jetbrains.kotlin.backend.konan.isRepresentedAs
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||||
@@ -15,14 +17,10 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
|||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
|
||||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.SimpleType
|
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
|
|
||||||
import org.jetbrains.kotlin.types.typeUtil.makeNullable
|
|
||||||
import org.jetbrains.kotlin.utils.singletonOrEmptyList
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boxes and unboxes values of primitive types when necessary.
|
* Boxes and unboxes values of value types when necessary.
|
||||||
*/
|
*/
|
||||||
internal class Autoboxing(val context: Context) : FileLoweringPass {
|
internal class Autoboxing(val context: Context) : FileLoweringPass {
|
||||||
|
|
||||||
@@ -40,27 +38,13 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr
|
|||||||
// is not equal to e.g. called function return type?
|
// is not equal to e.g. called function return type?
|
||||||
|
|
||||||
|
|
||||||
private val irBuiltins = context.irModule!!.irBuiltins
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of primitive types to box and unbox.
|
|
||||||
*/
|
|
||||||
private val primitiveTypes = with(context.builtIns) {
|
|
||||||
listOf(booleanType, byteType, shortType, charType, intType, longType, floatType, doubleType) +
|
|
||||||
unboundCallableReferenceTypeOrNull.singletonOrEmptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return type to use for runtime type checks instead of given one (e.g. `IntBox` instead of `Int`)
|
* @return type to use for runtime type checks instead of given one (e.g. `IntBox` instead of `Int`)
|
||||||
*/
|
*/
|
||||||
private fun getRuntimeReferenceType(type: KotlinType): KotlinType {
|
private fun getRuntimeReferenceType(type: KotlinType): KotlinType {
|
||||||
if (type.isSubtypeOf(builtIns.nullableNothingType)) return type
|
ValueType.values().forEach {
|
||||||
|
if (type.notNullableIsRepresentedAs(it)) {
|
||||||
primitiveTypes.forEach {
|
return getBoxType(it).makeNullableAsSpecified(TypeUtils.isNullableType(type))
|
||||||
listOf(it, it.makeNullable()).forEach { superType ->
|
|
||||||
if (type.isSubtypeOf(superType)) {
|
|
||||||
return getBoxType(superType).makeNullableAsSpecified(superType.isMarkedNullable)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,13 +93,13 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the element of [primitiveTypes] if given type is represented as primitive type in generated code,
|
* @return the [ValueType] given type represented in generated code as,
|
||||||
* or `null` if represented as object reference.
|
* or `null` if represented as object reference.
|
||||||
*/
|
*/
|
||||||
private fun getCustomRepresentation(type: KotlinType): KotlinType? {
|
private fun getValueType(type: KotlinType): ValueType? {
|
||||||
if (type.isSubtypeOf(builtIns.nothingType)) return null
|
return ValueType.values().firstOrNull {
|
||||||
|
type.isRepresentedAs(it)
|
||||||
return primitiveTypes.firstOrNull { type.isSubtypeOf(it) }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun IrExpression.useAs(type: KotlinType): IrExpression {
|
override fun IrExpression.useAs(type: KotlinType): IrExpression {
|
||||||
@@ -147,21 +131,21 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun IrExpression.adaptIfNecessary(actualType: KotlinType, expectedType: KotlinType): IrExpression {
|
private fun IrExpression.adaptIfNecessary(actualType: KotlinType, expectedType: KotlinType): IrExpression {
|
||||||
val actualRepresentation = getCustomRepresentation(actualType)
|
val actualValueType = getValueType(actualType)
|
||||||
val expectedRepresentation = getCustomRepresentation(expectedType)
|
val expectedValueType = getValueType(expectedType)
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
actualRepresentation == expectedRepresentation -> this
|
actualValueType == expectedValueType -> this
|
||||||
|
|
||||||
actualRepresentation == null && expectedRepresentation != null -> {
|
actualValueType == null && expectedValueType != null -> {
|
||||||
// This may happen in the following cases:
|
// This may happen in the following cases:
|
||||||
// 1. `actualType` is `Nothing`;
|
// 1. `actualType` is `Nothing`;
|
||||||
// 2. `actualType` is incompatible.
|
// 2. `actualType` is incompatible.
|
||||||
|
|
||||||
this.unbox(expectedRepresentation)
|
this.unbox(expectedValueType)
|
||||||
}
|
}
|
||||||
|
|
||||||
actualRepresentation != null && expectedRepresentation == null -> this.box(actualRepresentation)
|
actualValueType != null && expectedValueType == null -> this.box(actualValueType)
|
||||||
|
|
||||||
else -> throw IllegalArgumentException("actual type is $actualType, expected $expectedType")
|
else -> throw IllegalArgumentException("actual type is $actualType, expected $expectedType")
|
||||||
}
|
}
|
||||||
@@ -175,23 +159,24 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getBoxType(primitiveType: KotlinType): SimpleType {
|
private val ValueType.shortName
|
||||||
val primitiveTypeClass = TypeUtils.getClassDescriptor(primitiveType)!!
|
get() = this.classFqName.shortName()
|
||||||
return context.builtIns.getKonanInternalClass("${primitiveTypeClass.name}Box").defaultType
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun IrExpression.box(primitiveType: KotlinType): IrExpression {
|
private fun getBoxType(valueType: ValueType) =
|
||||||
val primitiveTypeClass = TypeUtils.getClassDescriptor(primitiveType)!!
|
context.builtIns.getKonanInternalClass("${valueType.shortName}Box").defaultType
|
||||||
val boxFunction = context.builtIns.getKonanInternalFunctions("box${primitiveTypeClass.name}").singleOrNull() ?:
|
|
||||||
TODO(primitiveType.toString())
|
private fun IrExpression.box(valueType: ValueType): IrExpression {
|
||||||
|
val boxFunctionName = "box${valueType.shortName}"
|
||||||
|
val boxFunction = context.builtIns.getKonanInternalFunctions(boxFunctionName).singleOrNull() ?:
|
||||||
|
TODO(valueType.toString())
|
||||||
|
|
||||||
return IrCallImpl(startOffset, endOffset, boxFunction).apply {
|
return IrCallImpl(startOffset, endOffset, boxFunction).apply {
|
||||||
putValueArgument(0, this@box)
|
putValueArgument(0, this@box)
|
||||||
}.uncheckedCast(this.type) // Try not to bring new type incompatibilities.
|
}.uncheckedCast(this.type) // Try not to bring new type incompatibilities.
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrExpression.unbox(primitiveType: KotlinType): IrExpression {
|
private fun IrExpression.unbox(valueType: ValueType): IrExpression {
|
||||||
val boxGetter = getBoxType(primitiveType)
|
val boxGetter = getBoxType(valueType)
|
||||||
.memberScope.getContributedDescriptors()
|
.memberScope.getContributedDescriptors()
|
||||||
.filterIsInstance<PropertyDescriptor>()
|
.filterIsInstance<PropertyDescriptor>()
|
||||||
.single { it.name.asString() == "value" }
|
.single { it.name.asString() == "value" }
|
||||||
|
|||||||
Reference in New Issue
Block a user