From 15107f4cdc1eb7d25d2ad6a3f28bbbec4a07e846 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Thu, 2 Feb 2017 14:31:02 +0700 Subject: [PATCH] backend: improve value types support: Introduce value type definition. Use the same type checking strategy in codegen and autoboxing. Optimize the latter by that. --- .../kotlin/backend/konan/ValueTypes.kt | 68 +++++++++++++++++ .../konan/descriptors/DescriptorUtils.kt | 17 +---- .../kotlin/backend/konan/llvm/DataLayout.kt | 38 ++++++---- .../kotlin/backend/konan/lower/Autoboxing.kt | 73 ++++++++----------- 4 files changed, 122 insertions(+), 74 deletions(-) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt new file mode 100644 index 00000000000..8e0d5235965 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt @@ -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) +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt index 47621613eda..916b7184969 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt @@ -1,6 +1,8 @@ package org.jetbrains.kotlin.backend.konan.descriptors 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.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.isFunctionType @@ -118,20 +120,7 @@ internal fun KonanBuiltIns.getKonanInternalFunctions(name: String): List get() { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt index 4f03338f621..7e7be223413 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt @@ -1,28 +1,34 @@ package org.jetbrains.kotlin.backend.konan.llvm import llvm.* -import org.jetbrains.kotlin.backend.konan.descriptors.isUnboundCallableReference -import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.backend.konan.ValueType +import org.jetbrains.kotlin.backend.konan.isRepresentedAs import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.isUnit -internal fun RuntimeAware.getLLVMType(type: KotlinType): LLVMTypeRef { - return when { - // Nullable types must be represented as objects for boxing. - type.isMarkedNullable -> this.kObjHeaderPtr - type.isUnboundCallableReference() -> int8TypePtr - KotlinBuiltIns.isBoolean(type) -> LLVMInt1Type() - KotlinBuiltIns.isByte(type) -> LLVMInt8Type() - KotlinBuiltIns.isShort(type) || KotlinBuiltIns.isChar(type) -> LLVMInt16Type() - KotlinBuiltIns.isInt(type) -> LLVMInt32Type() - KotlinBuiltIns.isLong(type) -> LLVMInt64Type() - KotlinBuiltIns.isFloat(type) -> LLVMFloatType() - KotlinBuiltIns.isDouble(type) -> LLVMDoubleType() - !KotlinBuiltIns.isPrimitiveType(type) -> this.kObjHeaderPtr - else -> throw NotImplementedError(type.toString() + " is not supported") +private val valueTypes = ValueType.values().associate { + it to when (it) { + ValueType.BOOLEAN -> LLVMInt1Type() + ValueType.BYTE -> LLVMInt8Type() + ValueType.SHORT, ValueType.CHAR -> LLVMInt16Type() + ValueType.INT -> LLVMInt32Type() + ValueType.LONG -> LLVMInt64Type() + ValueType.FLOAT -> LLVMFloatType() + ValueType.DOUBLE -> LLVMDoubleType() + ValueType.UNBOUND_CALLABLE_REFERENCE -> int8TypePtr }!! } +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 { return when { type.isUnit() -> LLVMVoidType()!! diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt index b920948d69e..cf619e512f5 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt @@ -3,9 +3,11 @@ package org.jetbrains.kotlin.backend.konan.lower import org.jetbrains.kotlin.backend.common.AbstractValueUsageTransformer import org.jetbrains.kotlin.backend.common.FileLoweringPass 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.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.PropertyDescriptor 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.visitors.transformChildrenVoid import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.SimpleType 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 { @@ -40,27 +38,13 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr // 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`) */ private fun getRuntimeReferenceType(type: KotlinType): KotlinType { - if (type.isSubtypeOf(builtIns.nullableNothingType)) return type - - primitiveTypes.forEach { - listOf(it, it.makeNullable()).forEach { superType -> - if (type.isSubtypeOf(superType)) { - return getBoxType(superType).makeNullableAsSpecified(superType.isMarkedNullable) - } + ValueType.values().forEach { + if (type.notNullableIsRepresentedAs(it)) { + return getBoxType(it).makeNullableAsSpecified(TypeUtils.isNullableType(type)) } } @@ -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. */ - private fun getCustomRepresentation(type: KotlinType): KotlinType? { - if (type.isSubtypeOf(builtIns.nothingType)) return null - - return primitiveTypes.firstOrNull { type.isSubtypeOf(it) } + private fun getValueType(type: KotlinType): ValueType? { + return ValueType.values().firstOrNull { + type.isRepresentedAs(it) + } } 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 { - val actualRepresentation = getCustomRepresentation(actualType) - val expectedRepresentation = getCustomRepresentation(expectedType) + val actualValueType = getValueType(actualType) + val expectedValueType = getValueType(expectedType) return when { - actualRepresentation == expectedRepresentation -> this + actualValueType == expectedValueType -> this - actualRepresentation == null && expectedRepresentation != null -> { + actualValueType == null && expectedValueType != null -> { // This may happen in the following cases: // 1. `actualType` is `Nothing`; // 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") } @@ -175,23 +159,24 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr return this } - private fun getBoxType(primitiveType: KotlinType): SimpleType { - val primitiveTypeClass = TypeUtils.getClassDescriptor(primitiveType)!! - return context.builtIns.getKonanInternalClass("${primitiveTypeClass.name}Box").defaultType - } + private val ValueType.shortName + get() = this.classFqName.shortName() - private fun IrExpression.box(primitiveType: KotlinType): IrExpression { - val primitiveTypeClass = TypeUtils.getClassDescriptor(primitiveType)!! - val boxFunction = context.builtIns.getKonanInternalFunctions("box${primitiveTypeClass.name}").singleOrNull() ?: - TODO(primitiveType.toString()) + private fun getBoxType(valueType: ValueType) = + context.builtIns.getKonanInternalClass("${valueType.shortName}Box").defaultType + + 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 { putValueArgument(0, this@box) }.uncheckedCast(this.type) // Try not to bring new type incompatibilities. } - private fun IrExpression.unbox(primitiveType: KotlinType): IrExpression { - val boxGetter = getBoxType(primitiveType) + private fun IrExpression.unbox(valueType: ValueType): IrExpression { + val boxGetter = getBoxType(valueType) .memberScope.getContributedDescriptors() .filterIsInstance() .single { it.name.asString() == "value" }