backend: implement autoboxing as IR transformation

This commit is contained in:
Svyatoslav Scherbina
2016-12-16 14:38:20 +07:00
committed by SvyatoslavScherbina
parent 3b7321ef94
commit 44f2036506
6 changed files with 497 additions and 27 deletions
@@ -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?
}
@@ -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<FunctionDescriptor> {
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
@@ -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)
}
}
}
@@ -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.
@@ -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<PropertyDescriptor>()
.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.
}
}
@@ -0,0 +1,49 @@
package konan.internal
// TODO: cache some boxes.
class BooleanBox(val value: Boolean) : Comparable<Boolean> {
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<Int> {
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.