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:
Svyatoslav Scherbina
2017-02-02 14:31:02 +07:00
committed by SvyatoslavScherbina
parent 31a0423225
commit 15107f4cdc
4 changed files with 122 additions and 74 deletions
@@ -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)
}
@@ -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() {
@@ -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()!!
@@ -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" }