diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/AbstractValueUsageTransformer.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/AbstractValueUsageTransformer.kt new file mode 100644 index 00000000000..306b94027db --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/AbstractValueUsageTransformer.kt @@ -0,0 +1,233 @@ +package org.jetbrains.kotlin.backend.common + +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.declarations.IrField +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.declarations.IrVariable +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.types.KotlinType + + +/** + * Transforms expressions depending on the context they are used in. + * + * The transformations are defined with `IrExpression.use*` methods in this class, + * the most common are [useAs], [useAsStatement], [useInTypeOperator]. + * + * TODO: the implementation is originally based on [org.jetbrains.kotlin.psi2ir.transformations.InsertImplicitCasts] + * and should probably be used as its base. + * + * TODO: consider making this visitor non-recursive to make it more general. + */ +abstract class AbstractValueUsageTransformer(val builtIns: KotlinBuiltIns): IrElementTransformerVoid() { + + protected open fun IrExpression.useAs(type: KotlinType): IrExpression = this + + protected open fun IrExpression.useAsStatement(): IrExpression = this + + protected open fun IrExpression.useInTypeOperator(operator: IrTypeOperator, typeOperand: KotlinType): IrExpression = + this + + protected open fun IrExpression.useAsValue(value: ValueDescriptor): IrExpression = this.useAs(value.type) + + protected open fun IrExpression.useAsArgument(parameter: ParameterDescriptor): IrExpression = + this.useAsValue(parameter) + + protected open fun IrExpression.useForVariable(variable: VariableDescriptor): IrExpression = + this.useAsValue(variable) + + protected open fun IrExpression.useForField(field: PropertyDescriptor): IrExpression = + this.useForVariable(field) + + protected open fun IrExpression.useAsReturnValue(returnTarget: CallableDescriptor): IrExpression { + val returnType = returnTarget.returnType ?: return this + return this.useAs(returnType) + } + + protected open fun IrExpression.useAsResult(enclosing: IrExpression): IrExpression = + this.useAs(enclosing.type) + + override fun visitMemberAccess(expression: IrMemberAccessExpression): IrExpression { + expression.transformChildrenVoid(this) + + with(expression) { + dispatchReceiver = dispatchReceiver?.useAsArgument(descriptor.dispatchReceiverParameter!!) + extensionReceiver = extensionReceiver?.useAsArgument(descriptor.extensionReceiverParameter!!) + for (index in descriptor.valueParameters.indices) { + val argument = getValueArgument(index) ?: continue + val parameter = descriptor.valueParameters[index] + putValueArgument(index, argument.useAsArgument(parameter)) + } + } + + return expression + } + + override fun visitBlockBody(body: IrBlockBody): IrBody { + body.transformChildrenVoid(this) + + body.statements.forEachIndexed { i, irStatement -> + if (irStatement is IrExpression) { + body.statements[i] = irStatement.useAsStatement() + } + } + + return body + } + + override fun visitContainerExpression(expression: IrContainerExpression): IrExpression { + expression.transformChildrenVoid(this) + + if (expression.statements.isEmpty()) { + return expression + } + + val lastIndex = expression.statements.lastIndex + expression.statements.forEachIndexed { i, irStatement -> + if (irStatement is IrExpression) { + expression.statements[i] = + if (i == lastIndex) + irStatement.useAsResult(expression) + else + irStatement.useAsStatement() + } + } + + return expression + } + + override fun visitReturn(expression: IrReturn): IrExpression { + expression.transformChildrenVoid(this) + + expression.value = expression.value.useAsReturnValue(expression.returnTarget) + + return expression + } + + override fun visitSetVariable(expression: IrSetVariable): IrExpression { + expression.transformChildrenVoid(this) + + expression.value = expression.value.useForVariable(expression.descriptor) + + return expression + } + + override fun visitSetField(expression: IrSetField): IrExpression { + expression.transformChildrenVoid(this) + + expression.value = expression.value.useForField(expression.descriptor) + + return expression + } + + override fun visitField(declaration: IrField): IrStatement { + declaration.transformChildrenVoid(this) + + declaration.initializer?.let { + it.expression = it.expression.useForField(declaration.descriptor) + } + + return declaration + } + + override fun visitVariable(declaration: IrVariable): IrVariable { + declaration.transformChildrenVoid(this) + + declaration.initializer = declaration.initializer?.useForVariable(declaration.descriptor) + + return declaration + } + + override fun visitWhen(expression: IrWhen): IrExpression { + expression.transformChildrenVoid(this) + + for (irBranch in expression.branches) { + irBranch.condition = irBranch.condition.useAs(builtIns.booleanType) + irBranch.result = irBranch.result.useAsResult(expression) + } + + return expression + } + + override fun visitLoop(loop: IrLoop): IrExpression { + loop.transformChildrenVoid(this) + + loop.condition = loop.condition.useAs(builtIns.booleanType) + + loop.body = loop.body?.useAsStatement() + + return loop + } + + override fun visitThrow(expression: IrThrow): IrExpression { + expression.transformChildrenVoid(this) + + expression.value = expression.value.useAs(builtIns.throwable.defaultType) + + return expression + } + + override fun visitTry(aTry: IrTry): IrExpression { + aTry.transformChildrenVoid(this) + + aTry.tryResult = aTry.tryResult.useAsResult(aTry) + + for (aCatch in aTry.catches) { + aCatch.result = aCatch.result.useAsResult(aTry) + } + + aTry.finallyExpression = aTry.finallyExpression?.useAsStatement() + + return aTry + } + + override fun visitVararg(expression: IrVararg): IrExpression { + expression.transformChildrenVoid(this) + + expression.elements.forEachIndexed { i, element -> + when (element) { + is IrSpreadElement -> + element.expression = element.expression.useAs(expression.type) + is IrExpression -> + expression.putElement(i, element.useAs(expression.varargElementType)) + } + } + + return expression + } + + override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression { + expression.transformChildrenVoid(this) + + expression.argument = expression.argument.useInTypeOperator(expression.operator, expression.typeOperand) + + return expression + } + + override fun visitFunction(declaration: IrFunction): IrStatement { + declaration.transformChildrenVoid(this) + + declaration.descriptor.valueParameters.forEach { parameter -> + val defaultValue = declaration.getDefault(parameter) + if (defaultValue is IrExpressionBody) { + defaultValue.expression = defaultValue.expression.useAsArgument(parameter) + } + } + + declaration.body?.let { + if (it is IrExpressionBody) { + it.expression = it.expression.useAsReturnValue(declaration.descriptor) + } + } + + return declaration + } + + // TODO: IrStringConcatenation, IrEnumEntry? + +} + diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DescriptorUtils.kt index 2f95b5b358c..73e84e621cf 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DescriptorUtils.kt @@ -8,6 +8,7 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.OverridingUtil import org.jetbrains.kotlin.resolve.descriptorUtil.* +import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.util.OperatorNameConventions @@ -45,7 +46,6 @@ internal val FunctionDescriptor.implementation: FunctionDescriptor private val intrinsicTypes = setOf( "kotlin.Unit", "kotlin.Boolean", "kotlin.Char", - "kotlin.Number", "kotlin.Byte", "kotlin.Short", "kotlin.Int", "kotlin.Long", "kotlin.Float", "kotlin.Double" @@ -74,23 +74,42 @@ internal val ClassDescriptor.isArray: Boolean internal val ClassDescriptor.isInterface: Boolean get() = (this.kind == ClassKind.INTERFACE) -private val konanInternal = FqName.fromSegments(listOf("konan", "internal")) +private val konanInternalPackageName = FqName.fromSegments(listOf("konan", "internal")) + +/** + * @return `konan.internal` member scope + */ +internal val KonanBuiltIns.konanInternal: MemberScope + get() = this.builtInsModule.getPackage(konanInternalPackageName).memberScope + +/** + * @return built-in class `konan.internal.$name` or + * `null` if no such class is available (e.g. when compiling `link` test without stdlib). + * + * TODO: remove this workaround after removing compilation without stdlib. + */ +internal fun KonanBuiltIns.getKonanInternalClassOrNull(name: String): ClassDescriptor? { + val classifier = konanInternal.getContributedClassifier(Name.identifier(name), NoLookupLocation.FROM_BACKEND) + return classifier as? ClassDescriptor +} /** * @return built-in class `konan.internal.$name` */ -internal fun KonanBuiltIns.getKonanInternalClass(name: String): ClassDescriptor { - val classifier = this.builtInsModule - .getPackage(konanInternal).memberScope - .getContributedClassifier(Name.identifier(name), NoLookupLocation.FROM_BACKEND) +internal fun KonanBuiltIns.getKonanInternalClass(name: String): ClassDescriptor = + getKonanInternalClassOrNull(name)!! - return classifier as ClassDescriptor +internal fun KonanBuiltIns.getKonanInternalFunctions(name: String): List { + return konanInternal.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND).toList() } private val UNBOUND_CALLABLE_REFERENCE = "UnboundCallableReference" -internal val KonanBuiltIns.unboundCallableReferenceType: KotlinType - get() = this.getKonanInternalClass(UNBOUND_CALLABLE_REFERENCE).defaultType +/** + * `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 diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt index a8bddce96c1..5f843d00b59 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt @@ -27,7 +27,7 @@ internal class KonanLower(val context: Context) { CallableReferenceLowering(context).runOnFilePostfix(irFile) } phaser.phase("Autobox") { - Autoboxing(context).runOnFilePostfix(irFile) + Autoboxing(context).lower(irFile) } } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index bcc30f5f683..d425435f7f7 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -1310,9 +1310,10 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid private fun evaluateCast(tmpVariableName: String, value: IrTypeOperatorCall): LLVMValueRef { logger.log("evaluateCast : ${ir2string(value)}") - assert(!KotlinBuiltIns.isPrimitiveType(value.type) && !KotlinBuiltIns.isPrimitiveType(value.argument.type)) + val type = value.typeOperand + assert(!KotlinBuiltIns.isPrimitiveType(type) && !KotlinBuiltIns.isPrimitiveType(value.argument.type)) - val dstDescriptor = TypeUtils.getClassDescriptor(value.type) // Get class descriptor for dst type. + val dstDescriptor = TypeUtils.getClassDescriptor(type) // Get class descriptor for dst type. val dstTypeInfo = codegen.typeInfoValue(dstDescriptor!!) // Get TypeInfo for dst type. val srcArg = evaluateExpression(codegen.newVar(), value.argument)!! // Evaluate src expression. val srcObjInfoPtr = codegen.bitcast(codegen.kObjHeaderPtr, srcArg, codegen.newVar()) // Cast src to ObjInfoPtr. 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 4aab8da3323..787f738ce4a 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 @@ -1,22 +1,190 @@ package org.jetbrains.kotlin.backend.konan.lower -import org.jetbrains.kotlin.backend.common.* -import org.jetbrains.kotlin.backend.konan.KonanBackendContext -import org.jetbrains.kotlin.backend.konan.llvm.ir2string -import org.jetbrains.kotlin.ir.declarations.* -import org.jetbrains.kotlin.ir.expressions.IrBody +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.getKonanInternalClass +import org.jetbrains.kotlin.backend.konan.getKonanInternalFunctions +import org.jetbrains.kotlin.backend.konan.unboundCallableReferenceTypeOrNull +import org.jetbrains.kotlin.descriptors.ParameterDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrTypeOperator +import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall +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 -internal class Autoboxing(val context: KonanBackendContext) : FunctionLoweringPass { +/** + * Boxes and unboxes values of primitive types when necessary. + */ +internal class Autoboxing(val context: Context) : FileLoweringPass { - var boxedCount = 0 - - /* - override fun lower(irBody: IrBody) { - println("Boxer body " + ir2string(irBody)) - } */ - - override fun lower(irFunction: IrFunction) { - println("Boxer function " + ir2string(irFunction)) + private val transformer = AutoboxingTransformer(context) + override fun lower(irFile: IrFile) { + irFile.transformChildrenVoid(transformer) } + } + +private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTransformer(context.builtIns) { + + // TODO: should we handle the cases when expression 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`) + */ + 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) + } + } + } + + return type + } + + override fun IrExpression.useInTypeOperator(operator: IrTypeOperator, typeOperand: KotlinType): IrExpression { + return if (operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT) { + this + } else { + // Codegen expects the argument of type-checking operator to be object reference: + this.useAs(builtIns.nullableAnyType) + } + } + + override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression { + super.visitTypeOperator(expression).let { + // Assume that the transformer doesn't replace the entire expression for simplicity: + assert (it === expression) + } + + val newTypeOperand = getRuntimeReferenceType(expression.typeOperand) + + return when (expression.operator) { + IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> expression + + IrTypeOperator.CAST, IrTypeOperator.IMPLICIT_CAST, + IrTypeOperator.IMPLICIT_NOTNULL, IrTypeOperator.SAFE_CAST -> { + + // Codegen produces the object reference: + val newExpressionType = builtIns.nullableAnyType + + IrTypeOperatorCallImpl(expression.startOffset, expression.endOffset, + newExpressionType, expression.operator, newTypeOperand, + expression.argument).useAs(expression.type) + } + + IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF -> if (newTypeOperand == expression.typeOperand) { + // Do not create new expression if nothing changes: + expression + } else { + IrTypeOperatorCallImpl(expression.startOffset, expression.endOffset, + expression.type, expression.operator, newTypeOperand, expression.argument) + } + } + } + + override fun IrExpression.useAsArgument(parameter: ParameterDescriptor): IrExpression { + return if (parameter.containingDeclaration == irBuiltins.eqeq) { + this // Do not box arguments of `==` because codegen expects them to be unboxed. + // TODO: implement `==` lowering at IR level and remove this hack. + } else { + this.useAsValue(parameter) + } + } + + /** + * @return the element of [primitiveTypes] if given type is represented as primitive type in generated code, + * 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) } + } + + override fun IrExpression.useAs(type: KotlinType): IrExpression { + return this.adaptIfNecessary(type) + } + + private fun IrExpression.adaptIfNecessary(expectedType: KotlinType): IrExpression { + val thisRepresentation = getCustomRepresentation(this.type) + val expectedRepresentation = getCustomRepresentation(expectedType) + + return when { + thisRepresentation == expectedRepresentation -> this + + thisRepresentation == null && expectedRepresentation != null -> { + // This may happen in the following cases: + // 1. `this.type` is `Nothing`; + // 2. `this` has the incompatible type. + + this.unbox(expectedRepresentation) + } + + thisRepresentation != null && expectedRepresentation == null -> this.box(thisRepresentation) + + else -> throw IllegalArgumentException("this is ${this.type}, expected $expectedType") + } + } + + /** + * Casts this expression to `type` without changing its representation in generated code. + */ + private fun IrExpression.uncheckedCast(type: KotlinType): IrExpression { + // TODO: apply some cast if types are incompatible; not required currently. + return this + } + + private fun getBoxType(primitiveType: KotlinType): SimpleType { + val primitiveTypeClass = TypeUtils.getClassDescriptor(primitiveType)!! + return context.builtIns.getKonanInternalClass("${primitiveTypeClass.name}Box").defaultType + } + + private fun IrExpression.box(primitiveType: KotlinType): IrExpression { + val primitiveTypeClass = TypeUtils.getClassDescriptor(primitiveType)!! + val boxFunction = context.builtIns.getKonanInternalFunctions("box${primitiveTypeClass.name}").single() + + 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) + .memberScope.getContributedDescriptors() + .filterIsInstance() + .single { it.name.asString() == "value" } + .getter!! + + return IrCallImpl(startOffset, endOffset, boxGetter).apply { + dispatchReceiver = this@unbox.uncheckedCast(boxGetter.dispatchReceiverParameter!!.type) + }.uncheckedCast(this.type) // Try not to bring new type incompatibilities. + } + +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/internal/Boxing.kt b/runtime/src/main/kotlin/konan/internal/Boxing.kt new file mode 100644 index 00000000000..59534ea2fc6 --- /dev/null +++ b/runtime/src/main/kotlin/konan/internal/Boxing.kt @@ -0,0 +1,49 @@ +package konan.internal + +// TODO: cache some boxes. + +class BooleanBox(val value: Boolean) : Comparable { + override fun equals(other: Any?): Boolean { + if (other !is BooleanBox) { + return false + } + + return this.value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + + override fun compareTo(other: Boolean): Int = value.compareTo(other) +} + +fun boxBoolean(value: Boolean) = BooleanBox(value) + +class IntBox(val value: Int) : Number(), Comparable { + override fun equals(other: Any?): Boolean { + if (other !is IntBox) { + return false + } + + return this.value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + + override fun compareTo(other: Int): Int = value.compareTo(other) + + override fun toByte() = value.toByte() + override fun toChar() = value.toChar() + override fun toShort() = value.toShort() + override fun toInt() = value.toInt() + override fun toLong() = value.toLong() + override fun toFloat() = value.toFloat() + override fun toDouble() = value.toDouble() +} + +fun boxInt(value: Int) = IntBox(value) + +// TODO: support other boxes. \ No newline at end of file