This commit is contained in:
Konstantin Anisimov
2017-02-03 10:37:20 +07:00
6 changed files with 123 additions and 76 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
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<Functio
return konanInternal.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND).toList()
}
private val UNBOUND_CALLABLE_REFERENCE = "UnboundCallableReference"
/**
* `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 fun KotlinType.isUnboundCallableReference() = this.isRepresentedAs(ValueType.UNBOUND_CALLABLE_REFERENCE)
internal val CallableDescriptor.allValueParameters: List<ParameterDescriptor>
get() {
@@ -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()!!
@@ -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<PropertyDescriptor>()
.single { it.name.asString() == "value" }
-1
View File
@@ -581,7 +581,6 @@ task boxing14(type: RunKonanTest) {
}
task boxing15(type: RunKonanTest) {
disabled = true
goldValue = "17\n"
source = "codegen/boxing/boxing15.kt"
}
+1 -1
View File
@@ -112,7 +112,7 @@ class PlatformInfo {
}
task dist_compiler(type: Copy) {
dependsOn ':backend.native:jars', 'dist_runtime'
dependsOn ':backend.native:jars'
destinationDir file('dist')