Implement inline classes in the JVM_IR backend

This commit is contained in:
Steven Schäfer
2019-05-10 12:18:28 +02:00
committed by Alexander Udalov
parent ad3e03bdbd
commit 9c957edcea
36 changed files with 1026 additions and 160 deletions
@@ -31,7 +31,7 @@ open class FrameMapBase<T : Any> {
var currentSize = 0
private set
fun enter(descriptor: T, type: Type): Int {
open fun enter(descriptor: T, type: Type): Int {
val index = currentSize
myVarIndex.put(descriptor, index)
currentSize += type.size
@@ -39,7 +39,7 @@ open class FrameMapBase<T : Any> {
return index
}
fun leave(descriptor: T): Int {
open fun leave(descriptor: T): Int {
val size = myVarSizes.get(descriptor)
currentSize -= size
myVarSizes.remove(descriptor)
@@ -449,7 +449,7 @@ public abstract class StackValue {
v.invokevirtual(methodOwner.getInternalName(), type.getClassName() + "Value", "()" + type.getDescriptor(), false);
}
private static void boxInlineClass(@NotNull KotlinType kotlinType, @NotNull InstructionAdapter v) {
public static void boxInlineClass(@NotNull KotlinType kotlinType, @NotNull InstructionAdapter v) {
Type boxedType = KotlinTypeMapper.mapInlineClassTypeAsDeclaration(kotlinType);
Type underlyingType = KotlinTypeMapper.mapUnderlyingTypeOfInlineClassType(kotlinType);
@@ -49,7 +49,7 @@ private fun getSignatureElementForMangling(type: KotlinType): String = buildStri
}
}
private fun md5base64(signatureForMangling: String): String {
fun md5base64(signatureForMangling: String): String {
val d = MessageDigest.getInstance("MD5").digest(signatureForMangling.toByteArray()).copyOfRange(0, 5)
// base64 URL encoder without padding uses exactly the characters allowed in both JVM bytecode and Dalvik bytecode names
return Base64.getUrlEncoder().withoutPadding().encodeToString(d)
@@ -170,10 +170,7 @@ fun IrTypeParameter.copyToWithoutSuperTypes(
}
}
fun IrFunction.copyParameterDeclarationsFrom(from: IrFunction) {
assert(typeParameters.isEmpty())
copyTypeParametersFrom(from)
fun IrFunction.copyValueParametersFrom(from: IrFunction) {
// TODO: should dispatch receiver be copied?
dispatchReceiverParameter = from.dispatchReceiverParameter?.let {
IrValueParameterImpl(it.startOffset, it.endOffset, it.origin, it.descriptor, it.type, it.varargElementType).also {
@@ -186,23 +183,33 @@ fun IrFunction.copyParameterDeclarationsFrom(from: IrFunction) {
valueParameters += from.valueParameters.map { it.copyTo(this, index = it.index + shift) }
}
fun IrTypeParametersContainer.copyTypeParametersFrom(
source: IrTypeParametersContainer,
fun IrFunction.copyParameterDeclarationsFrom(from: IrFunction) {
assert(typeParameters.isEmpty())
copyTypeParametersFrom(from)
copyValueParametersFrom(from)
}
fun IrTypeParametersContainer.copyTypeParameters(
srcTypeParameters: List<IrTypeParameter>,
origin: IrDeclarationOrigin? = null
) {
val target = this
val shift = target.typeParameters.size
val shift = typeParameters.size
// Any type parameter can figure in a boundary type for any other parameter.
// Therefore, we first copy the parameters themselves, then set up their supertypes.
source.typeParameters.forEachIndexed { i, sourceParameter ->
srcTypeParameters.forEachIndexed { i, sourceParameter ->
assert(sourceParameter.index == i)
target.typeParameters.add(sourceParameter.copyToWithoutSuperTypes(target, shift = shift, origin = origin ?: sourceParameter.origin))
typeParameters.add(sourceParameter.copyToWithoutSuperTypes(this, shift = shift, origin = origin ?: sourceParameter.origin))
}
source.typeParameters.zip(target.typeParameters.drop(shift)).forEach { (srcParameter, dstParameter) ->
srcTypeParameters.zip(typeParameters.drop(shift)).forEach { (srcParameter, dstParameter) ->
dstParameter.copySuperTypesFrom(srcParameter)
}
}
fun IrTypeParametersContainer.copyTypeParametersFrom(
source: IrTypeParametersContainer,
origin: IrDeclarationOrigin? = null
) = copyTypeParameters(source.typeParameters, origin)
private fun IrTypeParameter.copySuperTypesFrom(source: IrTypeParameter) {
val target = this
val sourceParent = source.parent as IrTypeParametersContainer
@@ -379,6 +386,9 @@ fun IrClass.simpleFunctions() = declarations.flatMap {
}
}
val IrClass.primaryConstructor: IrConstructor?
get() = constructors.singleOrNull(IrConstructor::isPrimary)
fun IrClass.createParameterDeclarations() {
assert (thisReceiver == null)
@@ -101,7 +101,7 @@ inline fun IrProperty.addSetter(b: IrFunctionBuilder.() -> Unit = {}): IrSimpleF
}
}
fun IrFunctionBuilder.buildFun(): IrSimpleFunction {
fun IrFunctionBuilder.buildFun(): IrFunctionImpl {
val wrappedDescriptor = WrappedSimpleFunctionDescriptor()
return IrFunctionImpl(
startOffset, endOffset, origin,
@@ -126,7 +126,7 @@ fun IrFunctionBuilder.buildConstructor(): IrConstructor {
}
}
inline fun buildFun(b: IrFunctionBuilder.() -> Unit): IrSimpleFunction =
inline fun buildFun(b: IrFunctionBuilder.() -> Unit): IrFunctionImpl =
IrFunctionBuilder().run {
b()
buildFun()
@@ -38,6 +38,7 @@ interface JvmLoweredDeclarationOrigin : IrDeclarationOrigin {
object GENERATED_PROPERTY_REFERENCE : IrDeclarationOriginImpl("GENERATED_PROPERTY_REFERENCE", isSynthetic = true)
object GENERATED_SAM_IMPLEMENTATION : IrDeclarationOriginImpl("GENERATED_SAM_IMPLEMENTATION", isSynthetic = true)
object ENUM_MAPPINGS_FOR_WHEN : IrDeclarationOriginImpl("ENUM_MAPPINGS_FOR_WHEN", isSynthetic = true)
object SYNTHETIC_INLINE_CLASS_MEMBER : IrDeclarationOriginImpl("SYNTHETIC_INLINE_CLASS_MEMBER", isSynthetic = true)
}
interface JvmLoweredStatementOrigin : IrStatementOrigin {
@@ -116,6 +116,7 @@ val jvmPhases = namedIrFilePhase<JvmBackendContext>(
tailrecPhase then
jvmDefaultConstructorPhase then
jvmInlineClassPhase then
defaultArgumentStubPhase then
interfacePhase then
@@ -257,4 +257,17 @@ class JvmSymbols(
val getOrCreateKotlinClasses: IrSimpleFunctionSymbol =
reflection.functions.single { it.owner.name.asString() == "getOrCreateKotlinClasses" }
val unsafeCoerceIntrinsic =
buildFun {
name = Name.special("<unsafe-coerce>")
origin = IrDeclarationOrigin.IR_BUILTINS_STUB
}.apply {
parent = kotlinJvmInternalPackage
val src = addTypeParameter("T", irBuiltIns.anyNType)
val dst = addTypeParameter("R", irBuiltIns.anyNType)
addValueParameter("v", src.defaultType)
returnType = dst.defaultType
}
val unsafeCoerceIntrinsicSymbol = unsafeCoerceIntrinsic.symbol
}
@@ -66,7 +66,7 @@ open class ClassCodegen protected constructor(
val type: Type = if (isAnonymous)
state.bindingContext.get(ASM_TYPE, descriptor)!!
else typeMapper.mapType(irClass)
else typeMapper.mapClass(irClass)
private val sourceManager = context.psiSourceManager
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.jvm.intrinsics.JavaClassProperty
import org.jetbrains.kotlin.backend.jvm.intrinsics.Not
import org.jetbrains.kotlin.backend.jvm.lower.CrIrType
import org.jetbrains.kotlin.backend.jvm.lower.constantValue
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.AsmUtil.*
import org.jetbrains.kotlin.codegen.inline.*
@@ -20,7 +21,10 @@ import org.jetbrains.kotlin.codegen.pseudoInsns.fakeAlwaysFalseIfeq
import org.jetbrains.kotlin.codegen.pseudoInsns.fixStackAndJump
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter
import org.jetbrains.kotlin.config.isReleaseCoroutines
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
@@ -28,9 +32,6 @@ import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
@@ -128,7 +129,7 @@ class ExpressionCodegen(
// Assume this expression's result has already been materialized on the stack
// with the correct type.
val IrExpression.onStack: MaterialValue
get() = MaterialValue(mv, asmType)
get() = MaterialValue(this@ExpressionCodegen, asmType, type)
private fun markNewLabel() = Label().apply { mv.visitLabel(this) }
@@ -146,9 +147,9 @@ class ExpressionCodegen(
}
// TODO remove
fun gen(expression: IrElement, type: Type, data: BlockInfo): StackValue {
expression.accept(this, data).coerce(type).materialize()
return StackValue.onStack(type)
fun gen(expression: IrExpression, type: Type, irType: IrType, data: BlockInfo): StackValue {
expression.accept(this, data).coerce(type, irType).materialize()
return StackValue.onStack(type, irType.toKotlinType())
}
fun generate() {
@@ -168,7 +169,7 @@ class ExpressionCodegen(
irFunction.markLineNumber(startOffset = irFunction is IrConstructor && irFunction.isPrimary)
}
val returnType = typeMapper.mapReturnType(irFunction)
result.coerce(returnType).materialize()
result.coerce(returnType, if (irFunction !is IrConstructor) irFunction.returnType else null).materialize()
mv.areturn(returnType)
}
val endLabel = markNewLabel()
@@ -190,7 +191,7 @@ class ExpressionCodegen(
private fun generateNonNullAssertion(param: IrValueParameter) {
val asmType = param.type.asmType
if (!param.type.isNullable() && !isPrimitive(asmType)) {
if (!param.type.unboxInlineClass().isNullable() && !isPrimitive(asmType)) {
mv.load(findLocalIndex(param.symbol), asmType)
mv.aconst(param.name.asString())
mv.invokestatic("kotlin/jvm/internal/Intrinsics", "checkParameterIsNotNull", "(Ljava/lang/Object;Ljava/lang/String;)V", false)
@@ -286,22 +287,23 @@ class ExpressionCodegen(
visitStatementContainer(body, data).discard()
override fun visitContainerExpression(expression: IrContainerExpression, data: BlockInfo) =
visitStatementContainer(expression, data).coerce(expression.asmType)
visitStatementContainer(expression, data).coerce(expression.type)
override fun visitFunctionAccess(expression: IrFunctionAccessExpression, data: BlockInfo): PromisedValue {
classCodegen.context.irIntrinsics.getIntrinsic(expression.symbol)
?.invoke(expression, this, data)?.let { return it.coerce(expression.asmType) }
?.invoke(expression, this, data)?.let { return it.coerce(expression.type) }
val isSuperCall = (expression as? IrCall)?.superQualifier != null
val callable = resolveToCallable(expression, isSuperCall)
val callee = expression.symbol.owner
val callGenerator = getOrCreateCallGenerator(expression, data)
val asmType = if (expression is IrConstructorCall) typeMapper.mapTypeAsDeclaration(expression.type) else expression.asmType
when {
expression is IrConstructorCall -> {
// IR constructors have no receiver and return the new instance, but on JVM they are void-returning
// instance methods named <init>.
mv.anew(expression.asmType)
mv.anew(asmType)
mv.dup()
}
expression is IrDelegatingConstructorCall ->
@@ -314,15 +316,23 @@ class ExpressionCodegen(
val receiver = expression.dispatchReceiver
receiver?.apply {
callGenerator.genValueAndPut(
null, this,
callee.dispatchReceiverParameter!!,
this,
if (isSuperCall) receiver.asmType else callable.dispatchReceiverType
?: throw AssertionError("No dispatch receiver type: ${expression.render()}"),
-1, this@ExpressionCodegen, data
this@ExpressionCodegen,
data
)
}
expression.extensionReceiver?.apply {
callGenerator.genValueAndPut(null, this, callable.extensionReceiverType!!, -1, this@ExpressionCodegen, data)
callGenerator.genValueAndPut(
callee.extensionReceiverParameter!!,
this,
callable.extensionReceiverType!!,
this@ExpressionCodegen,
data
)
}
callGenerator.beforeValueParametersStart()
@@ -344,7 +354,7 @@ class ExpressionCodegen(
val parameterType = callable.valueParameterTypes[i]
when {
arg != null -> {
callGenerator.genValueAndPut(irParameter, arg, parameterType, i, this@ExpressionCodegen, data)
callGenerator.genValueAndPut(irParameter, arg, parameterType, this@ExpressionCodegen, data)
}
irParameter.hasDefaultValue() -> {
callGenerator.putValueIfNeeded(
@@ -381,19 +391,22 @@ class ExpressionCodegen(
expression
)
val returnType = callee.returnType.substitute(typeSubstitutionMap)
val returnType = callee.returnType
return when {
returnType.isNothing() -> {
returnType.substitute(typeSubstitutionMap).isNothing() -> {
mv.aconst(null)
mv.athrow()
voidValue
}
expression is IrConstructorCall -> expression.onStack
expression is IrDelegatingConstructorCall -> voidValue
expression is IrConstructorCall ->
MaterialValue(this, asmType, expression.type)
expression is IrDelegatingConstructorCall ->
voidValue
expression.type.isUnit() ->
// NewInference allows casting `() -> T` to `() -> Unit`. A CHECKCAST here will fail.
MaterialValue(mv, callable.returnType).discard().coerce(expression.asmType)
else -> MaterialValue(mv, callable.returnType).coerce(expression.asmType)
MaterialValue(this, callable.returnType, returnType).discard().coerce(expression.type)
else ->
MaterialValue(this, callable.returnType, returnType).coerce(expression.type)
}
}
@@ -404,7 +417,7 @@ class ExpressionCodegen(
declaration.markLineNumber(startOffset = true)
declaration.initializer?.let {
it.accept(this, data).coerce(varType).materialize()
it.accept(this, data).coerce(varType, declaration.type).materialize()
mv.store(index, varType)
it.markLineNumber(startOffset = true)
}
@@ -418,8 +431,9 @@ class ExpressionCodegen(
// temporary variables. They do not correspond to variable loads in user code.
if (expression.symbol.owner.origin != IrDeclarationOrigin.IR_TEMPORARY_VARIABLE)
expression.markLineNumber(startOffset = true)
mv.load(findLocalIndex(expression.symbol), expression.asmType)
return expression.onStack
val type = frameMap.typeOf(expression.symbol)
mv.load(findLocalIndex(expression.symbol), type)
return MaterialValue(this, type, expression.type)
}
override fun visitFieldAccess(expression: IrFieldAccessExpression, data: BlockInfo): PromisedValue {
@@ -428,7 +442,7 @@ class ExpressionCodegen(
assert(expression is IrSetField) { "read of const val ${expression.symbol.owner.name} not inlined by ConstLowering" }
// This can only be the field's initializer; JVM implementations are required
// to generate those for ConstantValue-marked fields automatically, so this is redundant.
return voidValue.coerce(expression.asmType)
return voidValue.coerce(expression.type)
}
val realField = expression.symbol.owner.resolveFakeOverride()!!
@@ -439,18 +453,20 @@ class ExpressionCodegen(
expression.markLineNumber(startOffset = true)
expression.receiver?.accept(this, data)?.materialize()
return if (expression is IrSetField) {
expression.value.accept(this, data).coerce(fieldType).materialize()
expression.value.accept(this, data).coerce(fieldType, expression.symbol.owner.type).materialize()
when {
isStatic -> mv.putstatic(ownerType, fieldName, fieldType.descriptor)
else -> mv.putfield(ownerType, fieldName, fieldType.descriptor)
}
voidValue.coerce(expression.asmType)
voidValue.coerce(expression.type)
} else {
when {
isStatic -> mv.getstatic(ownerType, fieldName, fieldType.descriptor)
else -> mv.getfield(ownerType, fieldName, fieldType.descriptor)
isStatic ->
mv.getstatic(ownerType, fieldName, fieldType.descriptor)
else ->
mv.getfield(ownerType, fieldName, fieldType.descriptor)
}
MaterialValue(mv, fieldType).coerce(expression.asmType)
MaterialValue(this, fieldType, expression.symbol.owner.type).coerce(expression.type)
}
}
@@ -464,7 +480,7 @@ class ExpressionCodegen(
val isFieldInitializer = expression.origin == null
val skip = (inPrimaryConstructor || inClassInit) && isFieldInitializer && expressionValue is IrConst<*> &&
isDefaultValueForType(expression.symbol.owner.type.asmType, expressionValue.value)
return if (skip) voidValue.coerce(expression.asmType) else super.visitSetField(expression, data)
return if (skip) voidValue.coerce(expression.type) else super.visitSetField(expression, data)
}
/**
@@ -493,15 +509,15 @@ class ExpressionCodegen(
override fun visitSetVariable(expression: IrSetVariable, data: BlockInfo): PromisedValue {
expression.markLineNumber(startOffset = true)
expression.value.markLineNumber(startOffset = true)
expression.value.accept(this, data).coerce(expression.symbol.owner.asmType).materialize()
expression.value.accept(this, data).coerce(expression.symbol.owner.type).materialize()
mv.store(findLocalIndex(expression.symbol), expression.symbol.owner.asmType)
return voidValue.coerce(expression.asmType)
return voidValue.coerce(expression.type)
}
override fun <T> visitConst(expression: IrConst<T>, data: BlockInfo): PromisedValue {
expression.markLineNumber(startOffset = true)
when (val value = expression.value) {
is Boolean -> return object : BooleanValue(mv) {
is Boolean -> return object : BooleanValue(this) {
override fun jumpIfFalse(target: Label) = if (value) Unit else mv.goTo(target)
override fun jumpIfTrue(target: Label) = if (value) mv.goTo(target) else Unit
override fun materialize() = mv.iconst(if (value) 1 else 0)
@@ -551,7 +567,7 @@ class ExpressionCodegen(
Type.getType("[Ljava/lang/Object;")
else
Type.getType("[" + elementType.descriptor)
argument.accept(this, data).coerce(type).materialize()
argument.accept(this, data).coerce(type, argument.type).materialize()
mv.dup()
mv.arraylength()
mv.invokestatic("java/util/Arrays", "copyOf", Type.getMethodDescriptor(arrayType, arrayType, Type.INT_TYPE), false)
@@ -568,8 +584,8 @@ class ExpressionCodegen(
toArrayDescriptor = "([Ljava/lang/Object;)[Ljava/lang/Object;"
} else {
val spreadBuilderClassName =
AsmUtil.asmPrimitiveTypeToLangPrimitiveType(elementType)!!.typeName.identifier + "SpreadBuilder"
owner = "kotlin/jvm/internal/" + spreadBuilderClassName
asmPrimitiveTypeToLangPrimitiveType(elementType)!!.typeName.identifier + "SpreadBuilder"
owner = "kotlin/jvm/internal/$spreadBuilderClassName"
addDescriptor = "(" + elementType.descriptor + ")V"
toArrayDescriptor = "()" + type.descriptor
}
@@ -577,14 +593,14 @@ class ExpressionCodegen(
mv.dup()
mv.iconst(size)
mv.invokespecial(owner, "<init>", "(I)V", false)
for (i in 0..size - 1) {
for (i in 0 until size) {
mv.dup()
val argument = arguments[i]
if (argument is IrSpreadElement) {
argument.expression.accept(this, data).coerce(AsmTypes.OBJECT_TYPE).materialize()
argument.expression.accept(this, data).coerce(OBJECT_TYPE, argument.expression.type).materialize()
mv.invokevirtual(owner, "addSpread", "(Ljava/lang/Object;)V", false)
} else {
argument.accept(this, data).coerce(elementType).materialize()
argument.accept(this, data).coerce(elementType, expression.varargElementType).materialize()
mv.invokevirtual(owner, "add", addDescriptor, false)
}
}
@@ -604,7 +620,7 @@ class ExpressionCodegen(
for ((i, element) in expression.elements.withIndex()) {
mv.dup()
mv.iconst(i)
element.accept(this, data).coerce(elementType).materialize()
element.accept(this, data).coerce(elementType, expression.varargElementType).materialize()
mv.astore(elementType)
}
}
@@ -620,13 +636,21 @@ class ExpressionCodegen(
mv,
this
)
mv.newarray(boxType(elementIrType.asmType))
mv.newarray(typeMapper.boxType(elementIrType))
} else {
val type = typeMapper.mapType(arrayType)
mv.newarray(correctElementType(type))
mv.newarray(correctElementType(arrayType.asmType))
}
}
// Copied from FinallyBlocksLowering
private val IrReturnTarget.returnType: IrType
get() = when (this) {
is IrConstructor -> context.irBuiltIns.unitType
is IrFunction -> returnType
is IrReturnableBlock -> type
else -> error("Unknown ReturnTarget: $this")
}
override fun visitReturn(expression: IrReturn, data: BlockInfo): PromisedValue {
val owner = expression.returnTargetSymbol.owner
//TODO: should be owner != irFunction
@@ -644,7 +668,7 @@ class ExpressionCodegen(
val target = data.findBlock<ReturnableBlockInfo> { it.returnSymbol == expression.returnTargetSymbol }
val returnType = typeMapper.mapReturnType(owner)
val afterReturnLabel = Label()
expression.value.accept(this, data).coerce(returnType).materialize()
expression.value.accept(this, data).coerce(returnType, owner.returnType).materialize()
generateFinallyBlocksIfNeeded(returnType, afterReturnLabel, data, target)
expression.markLineNumber(startOffset = true)
if (target != null) {
@@ -680,7 +704,7 @@ class ExpressionCodegen(
} else {
branch.condition.accept(this, data).coerceToBoolean().jumpIfFalse(elseLabel)
}
val result = branch.result.accept(this, data).coerce(expression.asmType).materialized
val result = branch.result.accept(this, data).coerce(expression.type).materialized
if (branch.condition.isTrueConst()) {
// The rest of the expression is dead code.
mv.mark(endLabel)
@@ -691,14 +715,13 @@ class ExpressionCodegen(
}
// Produce the default value for the type. Doesn't really matter right now, as non-exhaustive
// conditionals cannot be used as expressions.
val result = voidValue.coerce(expression.asmType).materialized
val result = voidValue.coerce(expression.type).materialized
mv.mark(endLabel)
return result
}
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: BlockInfo): PromisedValue {
val typeOperand = expression.typeOperand
val asmType = typeOperand.asmType
val kotlinType = typeOperand.toKotlinType()
return when (expression.operator) {
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> {
@@ -708,30 +731,32 @@ class ExpressionCodegen(
}
IrTypeOperator.IMPLICIT_CAST, IrTypeOperator.IMPLICIT_INTEGER_COERCION -> {
expression.argument.accept(this, data).coerce(expression.asmType)
expression.argument.accept(this, data).coerce(expression.type)
}
IrTypeOperator.CAST, IrTypeOperator.SAFE_CAST -> {
expression.argument.accept(this, data).coerce(AsmTypes.OBJECT_TYPE).materialize()
val boxedType = boxType(asmType)
val result = expression.argument.accept(this, data)
val boxedLeftType = result.irType?.let { typeMapper.boxType(it) } ?: OBJECT_TYPE
result.coerce(boxedLeftType, expression.argument.type).materialize()
val boxedRightType = typeMapper.boxType(typeOperand)
if (typeOperand.isReifiedTypeParameter) {
putReifiedOperationMarkerIfTypeIsReifiedParameter(
typeOperand, if (IrTypeOperator.SAFE_CAST == expression.operator) SAFE_AS else AS, mv, this
)
v.checkcast(boxedType)
v.checkcast(boxedRightType)
} else {
generateAsCast(
mv, kotlinType, boxedType, expression.operator == IrTypeOperator.SAFE_CAST,
mv, kotlinType, boxedRightType, expression.operator == IrTypeOperator.SAFE_CAST,
state.languageVersionSettings.isReleaseCoroutines()
)
}
MaterialValue(mv, boxedType).coerce(expression.asmType)
MaterialValue(this, boxedRightType, expression.type).coerce(expression.type)
}
IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF -> {
expression.argument.accept(this, data).coerce(AsmTypes.OBJECT_TYPE).materialize()
val type = boxType(asmType)
expression.argument.accept(this, data).coerce(OBJECT_TYPE, context.irBuiltIns.anyNType).materialize()
val type = typeMapper.boxType(typeOperand)
if (typeOperand.isReifiedTypeParameter) {
putReifiedOperationMarkerIfTypeIsReifiedParameter(typeOperand, ReifiedTypeInliner.OperationKind.IS, mv, this)
v.instanceOf(type)
@@ -754,7 +779,7 @@ class ExpressionCodegen(
"(Ljava/lang/Object;Ljava/lang/String;)V", false
)
// Unbox primitives.
value.coerce(expression.asmType)
value.coerce(expression.type)
}
else -> throw AssertionError("type operator ${expression.operator} should have been lowered")
@@ -781,16 +806,19 @@ class ExpressionCodegen(
arity == 0 -> mv.aconst("")
arity == 1 -> {
// Convert single arg to string.
val type = expression.arguments[0].accept(this, data).materialized.type
if (!expression.arguments[0].type.isString())
AsmUtil.genToString(StackValue.onStack(type), type, null, typeMapper.kotlinTypeMapper).put(expression.asmType, mv)
val arg = expression.arguments[0]
val result = arg.accept(this, data).boxInlineClasses(arg.type).materialized
if (!arg.type.isString())
AsmUtil.genToString(StackValue.onStack(result.type), result.type, result.kotlinType, typeMapper.kotlinTypeMapper)
.put(expression.asmType, mv)
}
arity == 2 && expression.arguments[0].type.isStringClassType() -> {
// Call the stringPlus intrinsic
expression.arguments.forEach {
val type = it.accept(this, data).materialized.type
val result = it.accept(this, data).boxInlineClasses(it.type).materialized
val type = result.type
if (type.sort != Type.OBJECT)
AsmUtil.genToString(StackValue.onStack(type), type, null, typeMapper.kotlinTypeMapper).put(expression.asmType, mv)
AsmUtil.genToString(StackValue.onStack(type), type, result.kotlinType, typeMapper.kotlinTypeMapper).put(expression.asmType, mv)
}
mv.invokestatic(
IrIntrinsicMethods.INTRINSICS_CLASS_NAME,
@@ -801,9 +829,9 @@ class ExpressionCodegen(
}
else -> {
// Use StringBuilder to concatenate.
AsmUtil.genStringBuilderConstructor(mv)
genStringBuilderConstructor(mv)
expression.arguments.forEach {
AsmUtil.genInvokeAppendMethod(mv, it.accept(this, data).materialized.type, null)
genInvokeAppendMethod(mv, it.accept(this, data).boxInlineClasses(it.type).materialized.type, null)
}
mv.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false)
}
@@ -877,7 +905,7 @@ class ExpressionCodegen(
val isExpression = true //TODO: more wise check is required
var savedValue: Int? = null
if (isExpression) {
tryResult.coerce(tryAsmType).materialize()
tryResult.coerce(tryAsmType, aTry.type).materialize()
savedValue = frameMap.enterTemp(tryAsmType)
mv.store(savedValue, tryAsmType)
} else {
@@ -905,7 +933,7 @@ class ExpressionCodegen(
catchBody.markLineNumber(true)
val catchResult = catchBody.accept(this, data)
if (savedValue != null) {
catchResult.coerce(tryAsmType).materialize()
catchResult.coerce(tryAsmType, aTry.type).materialize()
mv.store(savedValue, tryAsmType)
} else {
catchResult.discard()
@@ -1027,14 +1055,13 @@ class ExpressionCodegen(
putJavaLangClassInstance(mv, classType.type, null, typeMapper.kotlinTypeMapper)
return classReference.onStack
} else {
val kotlinType = classType.toKotlinType()
val classifier = classType.classifierOrNull
if (classifier is IrTypeParameterSymbol) {
assert(classifier.owner.isReified) { "Non-reified type parameter under ::class should be rejected by type checker: ${classifier.owner.dump()}" }
putReifiedOperationMarkerIfTypeIsReifiedParameter(classType, ReifiedTypeInliner.OperationKind.JAVA_CLASS, mv, this)
}
putJavaLangClassInstance(mv, typeMapper.mapType(classType), kotlinType, typeMapper.kotlinTypeMapper)
putJavaLangClassInstance(mv, typeMapper.mapType(classType), classType.toKotlinType(), typeMapper.kotlinTypeMapper)
}
} else {
throw AssertionError("not an IrGetClass or IrClassReference: ${classReference.dump()}")
@@ -1046,9 +1073,8 @@ class ExpressionCodegen(
return classReference.onStack
}
private fun resolveToCallable(irCall: IrFunctionAccessExpression, isSuper: Boolean): Callable {
return typeMapper.mapToCallableMethod(irCall.symbol.owner, isSuper)
}
private fun resolveToCallable(irCall: IrFunctionAccessExpression, isSuper: Boolean) =
typeMapper.mapToCallableMethod(irCall.symbol.owner, isSuper)
private fun getOrCreateCallGenerator(element: IrFunctionAccessExpression, data: BlockInfo): IrCallGenerator {
if (!element.symbol.owner.isInlineFunctionCall(context)) {
@@ -1182,12 +1208,12 @@ class ExpressionCodegen(
fun DefaultCallArgs.generateOnStackIfNeeded(callGenerator: IrCallGenerator, isConstructor: Boolean, codegen: ExpressionCodegen): Boolean {
val toInts = toInts()
if (!toInts.isEmpty()) {
if (toInts.isNotEmpty()) {
for (mask in toInts) {
callGenerator.putValueIfNeeded(Type.INT_TYPE, StackValue.constant(mask, Type.INT_TYPE), ValueKind.DEFAULT_MASK, -1, codegen)
}
val parameterType = if (isConstructor) AsmTypes.DEFAULT_CONSTRUCTOR_MARKER else AsmTypes.OBJECT_TYPE
val parameterType = if (isConstructor) AsmTypes.DEFAULT_CONSTRUCTOR_MARKER else OBJECT_TYPE
callGenerator.putValueIfNeeded(
parameterType,
StackValue.constant(null, parameterType),
@@ -145,12 +145,17 @@ open class FunctionCodegen(
?.expression
}
private fun IrFrameMap.enterDispatchReceiver(parameter: IrValueParameter) {
val type = classCodegen.typeMapper.mapTypeAsDeclaration(parameter.type)
enter(parameter, type)
}
private fun createFrameMapWithReceivers(signature: JvmMethodSignature): IrFrameMap {
val frameMap = IrFrameMap()
if (irFunction is IrConstructor) {
frameMap.enter((irFunction.parent as IrClass).thisReceiver!!, AsmTypes.OBJECT_TYPE)
frameMap.enterDispatchReceiver(irFunction.constructedClass.thisReceiver!!)
} else if (irFunction.dispatchReceiverParameter != null) {
frameMap.enter(irFunction.dispatchReceiverParameter!!, AsmTypes.OBJECT_TYPE)
frameMap.enterDispatchReceiver(irFunction.dispatchReceiverParameter!!)
}
for (parameter in signature.valueParameters) {
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.codegen.ValueKind
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.org.objectweb.asm.Type
interface IrCallGenerator {
@@ -40,14 +41,13 @@ interface IrCallGenerator {
}
fun genValueAndPut(
irValueParameter: IrValueParameter?,
irValueParameter: IrValueParameter,
argumentExpression: IrExpression,
parameterType: Type,
parameterIndex: Int,
codegen: ExpressionCodegen,
blockInfo: BlockInfo
) {
codegen.gen(argumentExpression, parameterType, blockInfo)
codegen.gen(argumentExpression, parameterType, irValueParameter.type, blockInfo)
}
fun putValueIfNeeded(parameterType: Type, value: StackValue, kind: ValueKind, parameterIndex: Int, codegen: ExpressionCodegen) {
@@ -18,6 +18,8 @@ import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.util.getArguments
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.utils.keysToMap
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.Method
@@ -45,14 +47,13 @@ class IrInlineCodegen(
}
override fun genValueAndPut(
irValueParameter: IrValueParameter?,
irValueParameter: IrValueParameter,
argumentExpression: IrExpression,
parameterType: Type,
parameterIndex: Int,
codegen: ExpressionCodegen,
blockInfo: BlockInfo
) {
if (irValueParameter?.isInlineParameter() == true && isInlineIrExpression(argumentExpression)) {
if (irValueParameter.isInlineParameter() && isInlineIrExpression(argumentExpression)) {
val irReference: IrFunctionReference =
(argumentExpression as IrBlock).statements.filterIsInstance<IrFunctionReference>().single()
val boundReceiver = argumentExpression.statements.filterIsInstance<IrVariable>().singleOrNull()
@@ -65,7 +66,7 @@ class IrInlineCodegen(
activeLambda = null
}
} else {
putValueOnStack(argumentExpression, parameterType, irValueParameter?.index ?: -1, blockInfo)
putValueOnStack(argumentExpression, parameterType, irValueParameter.index, blockInfo, irValueParameter.type)
}
}
@@ -82,14 +83,14 @@ class IrInlineCodegen(
}
private fun putCapturedValueOnStack(argumentExpression: IrExpression, valueType: Type, capturedParamIndex: Int) {
val onStack = codegen.gen(argumentExpression, valueType, BlockInfo())
val onStack = codegen.gen(argumentExpression, valueType, argumentExpression.type, BlockInfo())
putArgumentOrCapturedToLocalVal(
JvmKotlinType(onStack.type, onStack.kotlinType), onStack, capturedParamIndex, capturedParamIndex, ValueKind.CAPTURED
)
}
private fun putValueOnStack(argumentExpression: IrExpression, valueType: Type, paramIndex: Int, blockInfo: BlockInfo) {
val onStack = codegen.gen(argumentExpression, valueType, blockInfo)
private fun putValueOnStack(argumentExpression: IrExpression, valueType: Type, paramIndex: Int, blockInfo: BlockInfo, irType: IrType) {
val onStack = codegen.gen(argumentExpression, valueType, irType, blockInfo)
putArgumentOrCapturedToLocalVal(JvmKotlinType(onStack.type, onStack.kotlinType), onStack, -1, paramIndex, ValueKind.CAPTURED)
}
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.backend.jvm.codegen
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.OwnerKind
import org.jetbrains.kotlin.codegen.signature.JvmSignatureWriter
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
@@ -57,9 +58,15 @@ class IrTypeMapper(val kotlinTypeMapper: KotlinTypeMapper) {
fun mapType(irType: IrType, sw: JvmSignatureWriter, mode: TypeMappingMode) =
kotlinTypeMapper.mapType(irType.toKotlinType(), sw, mode)
fun mapTypeAsDeclaration(irType: IrType) =
kotlinTypeMapper.mapTypeAsDeclaration(irType.toKotlinType())
fun mapTypeParameter(irType: IrType, signatureWriter: JvmSignatureWriter) =
kotlinTypeMapper.mapTypeParameter(irType.toKotlinType(), signatureWriter)
fun writeFormalTypeParameters(irParameters: List<IrTypeParameter>, sw: JvmSignatureWriter) =
kotlinTypeMapper.writeFormalTypeParameters(irParameters.map { it.descriptor }, sw)
}
fun boxType(irType: IrType) =
AsmUtil.boxType(mapType(irType), irType.toKotlinType(), kotlinTypeMapper)
}
@@ -5,28 +5,43 @@
package org.jetbrains.kotlin.backend.jvm.codegen
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.InlineClassAbi
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.toKotlinType
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
// A value that may not have been fully constructed yet. The ability to "roll back" code generation
// is useful for certain optimizations.
abstract class PromisedValue(val mv: InstructionAdapter, val type: Type) {
abstract class PromisedValue(val codegen: ExpressionCodegen, val type: Type, val irType: IrType?) {
// If this value is immaterial, construct an object on the top of the stack. This
// must always be done before generating other values or emitting raw bytecode.
abstract fun materialize()
val mv: InstructionAdapter
get() = codegen.mv
val typeMapper: IrTypeMapper
get() = codegen.typeMapper
val kotlinType: KotlinType?
get() = irType?.toKotlinType()
}
// A value that *has* been fully constructed.
class MaterialValue(mv: InstructionAdapter, type: Type) : PromisedValue(mv, type) {
class MaterialValue(codegen: ExpressionCodegen, type: Type, irType: IrType?) : PromisedValue(codegen, type, irType) {
override fun materialize() {}
}
// A value that can be branched on. JVM has certain branching instructions which can be used
// to optimize these.
abstract class BooleanValue(mv: InstructionAdapter) : PromisedValue(mv, Type.BOOLEAN_TYPE) {
abstract class BooleanValue(codegen: ExpressionCodegen) : PromisedValue(codegen, Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType) {
abstract fun jumpIfFalse(target: Label)
abstract fun jumpIfTrue(target: Label)
@@ -46,7 +61,7 @@ abstract class BooleanValue(mv: InstructionAdapter) : PromisedValue(mv, Type.BOO
val PromisedValue.materialized: MaterialValue
get() {
materialize()
return MaterialValue(mv, type)
return MaterialValue(codegen, type, irType)
}
// Materialize and disregard this value. Materialization is forced because, presumably,
@@ -55,22 +70,68 @@ fun PromisedValue.discard(): MaterialValue {
materialize()
if (type !== Type.VOID_TYPE)
AsmUtil.pop(mv, type)
return MaterialValue(mv, Type.VOID_TYPE)
return codegen.voidValue
}
private val IrType.unboxed: IrType
get() = InlineClassAbi.getUnderlyingType(erasedUpperBound)
fun PromisedValue.coerceInlineClasses(type: Type, irType: IrType, target: Type, irTarget: IrType): PromisedValue? {
val isFromTypeInlineClass = irType.erasedUpperBound.isInline
val isToTypeInlineClass = irTarget.erasedUpperBound.isInline
if (!isFromTypeInlineClass && !isToTypeInlineClass) return null
val isFromTypeUnboxed = isFromTypeInlineClass && typeMapper.mapType(irType.unboxed) == type
val isToTypeUnboxed = isToTypeInlineClass && typeMapper.mapType(irTarget.unboxed) == target
return when {
isFromTypeUnboxed && !isToTypeUnboxed -> object : PromisedValue(codegen, target, irTarget) {
override fun materialize() {
this@coerceInlineClasses.materialize()
// TODO: This is broken for type parameters
StackValue.boxInlineClass(irType.toKotlinType(), mv)
}
}
!isFromTypeUnboxed && isToTypeUnboxed -> object : PromisedValue(codegen, target, irTarget) {
override fun materialize() {
val value = this@coerceInlineClasses.materialized
StackValue.unboxInlineClass(value.type, irTarget.toKotlinType(), mv)
}
}
else -> null
}
}
// On materialization, cast the value to a different type.
fun PromisedValue.coerce(target: Type) = when (target) {
type -> this
else -> object : PromisedValue(mv, target) {
// TODO remove dependency
override fun materialize() = StackValue.coerce(this@coerce.materialized.type, type, mv)
fun PromisedValue.coerce(target: Type, irTarget: IrType? = null): PromisedValue {
if (irType != null && irTarget != null)
coerceInlineClasses(type, irType, target, irTarget)?.let { return it }
return when (type) {
// All unsafe coercions between irTypes should use the UnsafeCoerce intrinsic
target -> this
else -> object : PromisedValue(codegen, target, irTarget) {
override fun materialize() {
val value = this@coerce.materialized
StackValue.coerce(value.type, type, mv)
}
}
}
}
fun PromisedValue.coerce(irTarget: IrType) =
coerce(typeMapper.mapType(irTarget), irTarget)
fun PromisedValue.coerceToBoxed(irTarget: IrType) =
coerce(typeMapper.boxType(irTarget), irTarget)
fun PromisedValue.boxInlineClasses(irTarget: IrType) =
if (irTarget.classOrNull?.owner?.isInline == true)
coerceToBoxed(irTarget) else this
// Same as above, but with a return type that allows conditional jumping.
fun PromisedValue.coerceToBoolean() = when (val coerced = coerce(Type.BOOLEAN_TYPE)) {
is BooleanValue -> coerced
else -> object : BooleanValue(mv) {
else -> object : BooleanValue(codegen) {
override fun jumpIfFalse(target: Label) = coerced.materialize().also { mv.ifeq(target) }
override fun jumpIfTrue(target: Label) = coerced.materialize().also { mv.ifne(target) }
override fun materialize() = coerced.materialize()
@@ -78,4 +139,4 @@ fun PromisedValue.coerceToBoolean() = when (val coerced = coerce(Type.BOOLEAN_TY
}
val ExpressionCodegen.voidValue: MaterialValue
get() = MaterialValue(mv, Type.VOID_TYPE)
get() = MaterialValue(this, Type.VOID_TYPE, null)
@@ -222,12 +222,12 @@ class SwitchGenerator(private val expression: IrWhen, private val data: BlockInf
val endLabel = Label()
for ((thenExpression, label) in expressionToLabels) {
mv.visitLabel(label)
thenExpression.accept(codegen, data).coerce(expression.asmType).materialized
thenExpression.accept(codegen, data).coerce(expression.type).materialized
mv.goTo(endLabel)
}
mv.visitLabel(defaultLabel)
val stackValue = elseExpression?.accept(codegen, data) ?: voidValue
val result = stackValue.coerce(expression.asmType).materialized
val result = stackValue.coerce(expression.type).materialized
mv.mark(endLabel)
return result
}
@@ -43,7 +43,21 @@ import org.jetbrains.org.objectweb.asm.Type
import java.util.ArrayList
import java.util.LinkedHashSet
class IrFrameMap : FrameMapBase<IrSymbol>()
class IrFrameMap : FrameMapBase<IrSymbol>() {
private val typeMap = mutableMapOf<IrSymbol,Type>()
override fun enter(descriptor: IrSymbol, type: Type): Int {
typeMap[descriptor] = type
return super.enter(descriptor, type)
}
override fun leave(descriptor: IrSymbol): Int {
typeMap.remove(descriptor)
return super.leave(descriptor)
}
fun typeOf(descriptor: IrSymbol): Type = typeMap.getValue(descriptor)
}
internal val IrFunction.isStatic
get() = (this.dispatchReceiverParameter == null && this !is IrConstructor)
@@ -12,7 +12,7 @@ import org.jetbrains.org.objectweb.asm.Label
object AndAnd : IntrinsicMethod() {
private class BooleanConjunction(val arg0: IrExpression, val arg1: IrExpression, val codegen: ExpressionCodegen, val data: BlockInfo) : BooleanValue(codegen.mv) {
private class BooleanConjunction(val arg0: IrExpression, val arg1: IrExpression, codegen: ExpressionCodegen, val data: BlockInfo) : BooleanValue(codegen) {
override fun jumpIfFalse(target: Label) {
arg0.accept(codegen, data).coerceToBoolean().jumpIfFalse(target)
@@ -29,6 +29,6 @@ object Clone : IntrinsicMethod() {
assert(!AsmUtil.isPrimitive(result.type)) { "clone() of primitive type" }
val opcode = if (expression is IrCall && expression.superQualifier != null) Opcodes.INVOKESPECIAL else Opcodes.INVOKEVIRTUAL
mv.visitMethodInsn(opcode, "java/lang/Object", "clone", "()Ljava/lang/Object;", false)
MaterialValue(codegen.mv, AsmTypes.OBJECT_TYPE)
MaterialValue(codegen, AsmTypes.OBJECT_TYPE, context.irBuiltIns.anyNType)
}
}
@@ -30,7 +30,6 @@ import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import java.lang.UnsupportedOperationException
object CompareTo : IntrinsicMethod() {
private fun genInvoke(type: Type?, v: InstructionAdapter) {
@@ -64,7 +63,7 @@ object CompareTo : IntrinsicMethod() {
}
}
class BooleanComparison(val op: IElementType, val a: MaterialValue, val b: MaterialValue) : BooleanValue(a.mv) {
class BooleanComparison(val op: IElementType, val a: MaterialValue, val b: MaterialValue) : BooleanValue(a.codegen) {
override fun jumpIfFalse(target: Label) {
// TODO 1. get rid of the dependency; 2. take `b.type` into account.
val opcode = if (a.type.sort == Type.OBJECT)
@@ -90,8 +89,8 @@ class PrimitiveComparison(
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val parameterType = codegen.typeMapper.kotlinTypeMapper.mapType(primitiveNumberType)
val (left, right) = expression.receiverAndArgs()
val a = left.accept(codegen, data).coerce(parameterType).materialized
val b = right.accept(codegen, data).coerce(parameterType).materialized
val a = left.accept(codegen, data).coerce(parameterType, left.type).materialized
val b = right.accept(codegen, data).coerce(parameterType, right.type).materialized
return BooleanComparison(operatorToken, a, b)
}
}
@@ -16,10 +16,12 @@ import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.types.toKotlinType
import org.jetbrains.kotlin.ir.util.isInlined
import org.jetbrains.kotlin.ir.util.isNullConst
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.types.isNullable
import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberOrNullableType
import org.jetbrains.kotlin.types.typeUtil.upperBoundedByPrimitiveNumberOrNullableType
import org.jetbrains.org.objectweb.asm.Label
@@ -27,7 +29,7 @@ import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class Equals(val operator: IElementType) : IntrinsicMethod() {
private class BooleanNullCheck(val value: PromisedValue) : BooleanValue(value.mv) {
private class BooleanNullCheck(val value: PromisedValue) : BooleanValue(value.codegen) {
override fun jumpIfFalse(target: Label) = value.materialize().also { mv.ifnonnull(target) }
override fun jumpIfTrue(target: Label) = value.materialize().also { mv.ifnull(target) }
}
@@ -42,15 +44,16 @@ class Equals(val operator: IElementType) : IntrinsicMethod() {
val rightType = with(codegen) { b.asmType }
val opToken = expression.origin
val useEquals = opToken !== IrStatementOrigin.EQEQEQ && opToken !== IrStatementOrigin.EXCLEQEQ &&
(a.type.isInlined() || b.type.isInlined() ||
// `==` is `equals` for objects and floating-point numbers. In the latter case, the difference
// is that `equals` is a total order (-0 < +0 and NaN == NaN) and `===` is IEEE754-compliant.
(!isPrimitive(leftType) || leftType != rightType || leftType == Type.FLOAT_TYPE || leftType == Type.DOUBLE_TYPE)
!isPrimitive(leftType) || leftType != rightType || leftType == Type.FLOAT_TYPE || leftType == Type.DOUBLE_TYPE)
val operandType = if (!isPrimitive(leftType) || useEquals) AsmTypes.OBJECT_TYPE else leftType
val aValue = a.accept(codegen, data).coerce(operandType).materialized
val bValue = b.accept(codegen, data).coerce(operandType).materialized
val aValue = a.accept(codegen, data).coerce(operandType, a.type).materialized
val bValue = b.accept(codegen, data).coerce(operandType, b.type).materialized
if (useEquals) {
AsmUtil.genAreEqualCall(codegen.mv)
return MaterialValue(codegen.mv, Type.BOOLEAN_TYPE)
return MaterialValue(codegen, Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType)
}
return BooleanComparison(operator, aValue, bValue)
}
@@ -86,8 +89,8 @@ class Ieee754Equals(val operandType: Type) : IntrinsicMethod() {
if (!arg1Type.isPrimitiveNumberOrNullableType() && !arg1Type.upperBoundedByPrimitiveNumberOrNullableType())
throw AssertionError("Should be primitive or nullable primitive type: $arg1Type")
val arg0isNullable = arg0Type.isMarkedNullable
val arg1isNullable = arg1Type.isMarkedNullable
val arg0isNullable = arg0Type.isNullable()
val arg1isNullable = arg1Type.isNullable()
return when {
!arg0isNullable && !arg1isNullable ->
@@ -35,7 +35,7 @@ object HashCode : IntrinsicMethod() {
irFunction.origin == IrDeclarationOrigin.GENERATED_INLINE_CLASS_MEMBER || irFunction.origin == IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER ->
AsmUtil.genHashCode(mv, mv, result.type, target)
target == JvmTarget.JVM_1_6 -> {
result.coerce(AsmUtil.boxType(result.type)).materialize()
result.coerceToBoxed(receiver.type).materialize()
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "hashCode", "()I", false)
}
else -> {
@@ -49,6 +49,6 @@ object HashCode : IntrinsicMethod() {
)
}
}
MaterialValue(codegen.mv, Type.INT_TYPE)
MaterialValue(codegen, Type.INT_TYPE, codegen.context.irBuiltIns.intType)
}
}
@@ -28,7 +28,7 @@ abstract class IntrinsicMethod {
with(codegen) {
val descriptor = typeMapper.mapSignatureSkipGeneric(expression.symbol.owner)
val stackValue = toCallable(expression, descriptor, context).invoke(mv, codegen, data)
return object : PromisedValue(mv, stackValue.type) {
return object : PromisedValue(this, stackValue.type, expression.type) {
override fun materialize() = stackValue.put(mv)
}
}
@@ -39,7 +39,7 @@ object IrEnumValueOf : IntrinsicMethod() {
return object : IrIntrinsicFunction(expression, newSignature, context, listOf(stringType)) {
override fun invoke(v: InstructionAdapter, codegen: ExpressionCodegen, data: BlockInfo): StackValue {
v.tconst(enumType)
codegen.gen(expression.getValueArgument(0)!!, stringType, data)
codegen.gen(expression.getValueArgument(0)!!, stringType, codegen.context.irBuiltIns.stringType, data)
v.invokestatic("java/lang/Enum", "valueOf", "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;", false)
v.checkcast(enumType)
return StackValue.onStack(enumType)
@@ -92,7 +92,7 @@ open class IrIntrinsicFunction(
}
private fun genArg(expression: IrExpression, codegen: ExpressionCodegen, index: Int, data: BlockInfo) {
codegen.gen(expression, argsTypes[index], data)
codegen.gen(expression, argsTypes[index], expression.type, data)
}
companion object {
@@ -102,7 +102,8 @@ class IrIntrinsicMethods(val irBuiltIns: IrBuiltIns, val symbols: JvmSymbols) {
irBuiltIns.illegalArgumentExceptionSymbol.toKey()!! to IrIllegalArgumentException,
irBuiltIns.throwNpeSymbol.toKey()!! to ThrowNPE,
irBuiltIns.andandSymbol.toKey()!! to AndAnd,
irBuiltIns.ororSymbol.toKey()!! to OrOr
irBuiltIns.ororSymbol.toKey()!! to OrOr,
symbols.unsafeCoerceIntrinsicSymbol.toKey()!! to UnsafeCoerce
) +
numberConversionMethods() +
unaryFunForPrimitives("plus", UnaryPlus) +
@@ -20,23 +20,30 @@ import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.codegen.AsmUtil.boxType
import org.jetbrains.kotlin.codegen.AsmUtil.isPrimitive
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.util.isInlined
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.org.objectweb.asm.Type
object JavaClassProperty : IntrinsicMethod() {
fun invokeWith(value: PromisedValue) {
if (value.type == Type.VOID_TYPE) {
return invokeWith(value.coerce(AsmTypes.UNIT_TYPE))
}
if (isPrimitive(value.type)) {
value.discard()
value.mv.getstatic(boxType(value.type).internalName, "TYPE", "Ljava/lang/Class;")
} else {
value.materialize()
value.mv.invokevirtual("java/lang/Object", "getClass", "()Ljava/lang/Class;", false)
}
private fun invokeGetClass(value: PromisedValue) {
value.materialize()
value.mv.invokevirtual("java/lang/Object", "getClass", "()Ljava/lang/Class;", false)
}
fun invokeWith(value: PromisedValue) =
when {
value.type == Type.VOID_TYPE ->
invokeGetClass(value.coerce(AsmTypes.UNIT_TYPE))
value.irType?.isInlined() == true ->
invokeGetClass(value.coerceToBoxed(value.irType))
isPrimitive(value.type) -> {
value.discard()
value.mv.getstatic(boxType(value.type).internalName, "TYPE", "Ljava/lang/Class;")
}
else ->
invokeGetClass(value)
}
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
invokeWith(expression.extensionReceiver!!.accept(codegen, data))
return with(codegen) { expression.onStack }
@@ -13,7 +13,7 @@ import org.jetbrains.org.objectweb.asm.Type
object NewArray : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
codegen.gen(expression.getValueArgument(0)!!, Type.INT_TYPE, data)
codegen.gen(expression.getValueArgument(0)!!, Type.INT_TYPE, codegen.context.irBuiltIns.intType, data)
codegen.newArrayInstruction(expression.type)
return with(codegen) { expression.onStack }
}
@@ -24,7 +24,7 @@ import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.org.objectweb.asm.Label
object Not : IntrinsicMethod() {
class BooleanNegation(val value: BooleanValue) : BooleanValue(value.mv) {
class BooleanNegation(val value: BooleanValue) : BooleanValue(value.codegen) {
override fun jumpIfFalse(target: Label) = value.jumpIfTrue(target)
override fun jumpIfTrue(target: Label) = value.jumpIfFalse(target)
}
@@ -13,7 +13,7 @@ import org.jetbrains.org.objectweb.asm.Label
object OrOr : IntrinsicMethod() {
private class BooleanDisjunction(
val arg0: IrExpression, val arg1: IrExpression, val codegen: ExpressionCodegen, val data: BlockInfo) : BooleanValue(codegen.mv) {
val arg0: IrExpression, val arg1: IrExpression, codegen: ExpressionCodegen, val data: BlockInfo) : BooleanValue(codegen) {
override fun jumpIfFalse(target: Label) {
val stayLabel = Label()
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
/**
* Implicit coercion between IrTypes with the same underlying representation.
*
* A call of the form `coerce<A,B>(x)` allows us to coerce the value of `x` to type `A` but treat the result as if
* it had IrType `B`. This is useful for inline classes, whose coercion behavior depends on the IrType in
* addition to the underlying asmType.
*/
object UnsafeCoerce : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val from = expression.getTypeArgument(0)!!
val to = expression.getTypeArgument(1)!!
require(with(codegen) { typeMapper.mapType(from) == typeMapper.mapType(to) })
val arg = expression.getValueArgument(0)!!
val result = arg.accept(codegen, data)
return object : PromisedValue(codegen, codegen.typeMapper.mapType(to), to) {
override fun materialize() = result.coerce(from).materialize()
}
}
}
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.codegen.isInlineFunctionCall
import org.jetbrains.kotlin.backend.jvm.codegen.isInlineIrExpression
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.isPrimaryInlineClassConstructor
import org.jetbrains.kotlin.codegen.PropertyReferenceCodegen
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
@@ -341,6 +342,18 @@ internal class CallableReferenceLowering(val context: JvmBackendContext) : FileL
}
}
private fun IrSimpleFunction.invokeInlineClassConstructor(irConstructor: IrConstructor): IrBody {
val irBuilder = context.createIrBuilder(symbol, startOffset, endOffset)
val param = irConstructor.valueParameters[0].copyTo(this)
valueParameters.add(param)
val from = irConstructor.valueParameters[0].type
val to = irConstructor.returnType
val call = irBuilder.irCall(context.ir.symbols.unsafeCoerceIntrinsicSymbol, to, listOf(from, to)).apply {
putValueArgument(0, irBuilder.irGet(param))
}
return irBuilder.irExprBody(call)
}
private fun createInvokeMethod(superFunction: IrSimpleFunction): IrSimpleFunction =
buildFun {
setSourceRange(irFunctionReference)
@@ -357,6 +370,11 @@ internal class CallableReferenceLowering(val context: JvmBackendContext) : FileL
dispatchReceiverParameter = functionReferenceClass.thisReceiver?.copyTo(function)
if (callee.isPrimaryInlineClassConstructor) {
body = invokeInlineClassConstructor(callee as IrConstructor)
return this
}
val unboundArgsSet = unboundCalleeParameters.toSet()
if (useVararg) {
valueParameters.add(superFunction.valueParameters[0].copyTo(function))
@@ -432,11 +450,7 @@ internal class CallableReferenceLowering(val context: JvmBackendContext) : FileL
irGet(valueParameters[unboundIndex++])
}
}
when (parameter) {
callee.dispatchReceiverParameter -> dispatchReceiver = argument
callee.extensionReceiverParameter -> extensionReceiver = argument
else -> putValueArgument(parameter.index, argument)
}
putArgument(callee, parameter, argument)
}
if (!useVararg) assert(unboundIndex == valueParameters.size) { "Not all arguments of <invoke> are used" }
@@ -0,0 +1,389 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.ir.copyParameterDeclarationsFrom
import org.jetbrains.kotlin.backend.common.ir.passTypeArgumentsFrom
import org.jetbrains.kotlin.backend.common.ir.primaryConstructor
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irBlockBody
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.*
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.addConstructor
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrSetVariableImpl
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.symbols.IrVariableSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
val jvmInlineClassPhase = makeIrFilePhase(
::JvmInlineClassLowering,
name = "Inline Classes",
description = "Lower inline classes"
)
/**
* Adds new constructors, box, and unbox functions to inline classes as well as replacement
* functions and bridges to avoid clashes between overloaded function. Changes calls with
* known types to call the replacement functions.
*
* We do not unfold inline class types here. Instead, the type mapper will lower inline class
* types to the types of their underlying field.
*/
private class JvmInlineClassLowering(private val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoid() {
private val manager = MemoizedInlineClassReplacements()
private val valueMap = mutableMapOf<IrValueSymbol, IrValueDeclaration>()
override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid()
}
override fun visitClass(declaration: IrClass): IrStatement {
// The arguments to the primary constructor are in scope in the initializers of IrFields.
declaration.primaryConstructor?.let {
manager.getReplacementFunction(it)?.let { replacement ->
valueMap.putAll(replacement.valueParameterMap)
}
}
declaration.transformDeclarationsFlat { memberDeclaration ->
if (memberDeclaration is IrFunction) {
transformFunctionFlat(memberDeclaration)
} else {
memberDeclaration.accept(this, null)
null
}
}
if (declaration.isInline) {
val irConstructor = declaration.primaryConstructor!!
declaration.declarations.removeIf {
(it is IrConstructor && it.isPrimary) || (it is IrFunction && it.isInlineClassFieldGetter)
}
buildPrimaryInlineClassConstructor(declaration, irConstructor)
buildBoxFunction(declaration)
buildUnboxFunction(declaration)
}
return declaration
}
private fun transformFunctionFlat(function: IrFunction): List<IrDeclaration>? {
if (function.isPrimaryInlineClassConstructor)
return null
val replacement = manager.getReplacementFunction(function)
if (replacement == null) {
function.transformChildrenVoid()
return null
}
return when (function) {
is IrSimpleFunction -> transformSimpleFunctionFlat(function, replacement)
is IrConstructor -> transformConstructorFlat(function, replacement)
else -> throw IllegalStateException()
}
}
private fun transformSimpleFunctionFlat(function: IrSimpleFunction, replacement: IrReplacementFunction): List<IrDeclaration> {
val worker = replacement.function
valueMap.putAll(replacement.valueParameterMap)
worker.valueParameters.forEach { it.transformChildrenVoid() }
worker.body = function.body?.transform(this, null)?.patchDeclarationParents(worker)
// Don't create a wrapper for functions which are only used in an unboxed context
if (function.overriddenSymbols.isEmpty() || worker.dispatchReceiverParameter != null)
return listOf(worker)
// Replace the function body with a wrapper
context.createIrBuilder(function.symbol).run {
val call = irCall(worker).apply {
passTypeArgumentsFrom(function)
for (parameter in function.explicitParameters) {
val newParameter = replacement.valueParameterMap[parameter.symbol] ?: continue
putArgument(newParameter, irGet(parameter))
}
}
function.body = irExprBody(call)
}
return listOf(worker, function)
}
// Secondary constructors for boxed types get translated to static functions returning
// unboxed arguments. We remove the original constructor.
private fun transformConstructorFlat(constructor: IrConstructor, replacement: IrReplacementFunction): List<IrDeclaration> {
val worker = replacement.function
valueMap.putAll(replacement.valueParameterMap)
worker.valueParameters.forEach { it.transformChildrenVoid() }
worker.body = context.createIrBuilder(worker.symbol).irBlockBody(worker) {
val thisVar = irTemporaryVarDeclaration(
worker.returnType, nameHint = "\$this", isMutable = false
)
valueMap[constructor.constructedClass.thisReceiver!!.symbol] = thisVar
constructor.body?.contents?.forEach { statement ->
+statement
.transform(object : IrElementTransformerVoid() {
// Don't recurse under nested class declarations
override fun visitClass(declaration: IrClass): IrStatement {
return declaration
}
// Capture the result of a delegating constructor call in a temporary variable "thisVar".
//
// Within the constructor we replace references to "this" with references to "thisVar".
// This is safe, since the delegating constructor call precedes all references to "this".
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression {
expression.transformChildrenVoid()
return irSetVar(thisVar.symbol, expression)
}
// A constructor body has type unit and may contain explicit return statements.
// These early returns may have side-effects however, so we still have to evaluate
// the return expression. Afterwards we return "thisVar".
// For example, the following is a valid inline class declaration.
//
// inline class Foo(val x: String) {
// constructor(y: Int) : this(y.toString()) {
// if (y == 0) return throw java.lang.IllegalArgumentException()
// if (y == 1) return
// return Unit
// }
// }
override fun visitReturn(expression: IrReturn): IrExpression {
expression.transformChildrenVoid()
if (expression.returnTargetSymbol != constructor.symbol)
return expression
return irReturn(irBlock(expression.startOffset, expression.endOffset) {
+expression.value
+irGet(thisVar)
})
}
}, null)
.transform(this@JvmInlineClassLowering, null)
.patchDeclarationParents(worker)
}
+irReturn(irGet(thisVar))
}
return listOf(worker)
}
private fun typedArgumentList(function: IrFunction, expression: IrMemberAccessExpression) =
listOfNotNull(
function.dispatchReceiverParameter?.let { it to expression.dispatchReceiver },
function.extensionReceiverParameter?.let { it to expression.extensionReceiver }
) + function.valueParameters.map { it to expression.getValueArgument(it.index) }
private fun IrMemberAccessExpression.buildReplacement(
originalFunction: IrFunction,
original: IrMemberAccessExpression,
replacement: IrReplacementFunction
) {
copyTypeArgumentsFrom(original)
for ((parameter, argument) in typedArgumentList(originalFunction, original)) {
if (argument == null) continue
val newParameter = replacement.valueParameterMap[parameter.symbol] ?: continue
putArgument(
replacement.function,
newParameter,
argument.transform(this@JvmInlineClassLowering, null)
)
}
}
private fun buildReplacementCall(
originalFunction: IrFunction,
original: IrFunctionAccessExpression,
replacement: IrReplacementFunction
) = context.createIrBuilder(original.symbol)
.irCall(replacement.function)
.apply { buildReplacement(originalFunction, original, replacement) }
override fun visitFunctionReference(expression: IrFunctionReference): IrExpression {
val replacement = manager.getReplacementFunction(expression.symbol.owner)
?: return super.visitFunctionReference(expression)
val function = replacement.function
return IrFunctionReferenceImpl(
expression.startOffset, expression.endOffset, expression.type,
function.symbol, function.descriptor,
function.typeParameters.size, function.valueParameters.size,
expression.origin
).apply {
buildReplacement(expression.symbol.owner, expression, replacement)
}
}
override fun visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression {
val function = expression.symbol.owner
val replacement = manager.getReplacementFunction(function)
?: return super.visitFunctionAccess(expression)
return buildReplacementCall(function, expression, replacement)
}
private fun coerceInlineClasses(argument: IrExpression, from: IrType, to: IrType) =
IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, to, context.ir.symbols.unsafeCoerceIntrinsicSymbol).apply {
putTypeArgument(0, from)
putTypeArgument(1, to)
putValueArgument(0, argument)
}
override fun visitCall(expression: IrCall): IrExpression {
val function = expression.symbol.owner
if (!function.isInlineClassFieldGetter)
return super.visitCall(expression)
val arg = expression.dispatchReceiver!!.transform(this, null)
return coerceInlineClasses(arg, function.dispatchReceiverParameter!!.type, expression.type)
}
override fun visitGetField(expression: IrGetField): IrExpression {
val field = expression.symbol.owner
val parent = field.parent
if (parent is IrClass && parent.isInline && field.name == parent.inlineClassFieldName) {
val receiver = expression.receiver!!.transform(this, null)
return coerceInlineClasses(receiver, receiver.type, field.type)
}
return super.visitGetField(expression)
}
override fun visitReturn(expression: IrReturn): IrExpression {
expression.returnTargetSymbol.owner.safeAs<IrFunction>()?.let { target ->
manager.getReplacementFunction(target)?.let {
return context.createIrBuilder(it.function.symbol).irReturn(
expression.value.transform(this, null)
)
}
}
return super.visitReturn(expression)
}
private fun visitStatementContainer(container: IrStatementContainer) {
container.statements.transformFlat { statement ->
if (statement is IrFunction)
transformFunctionFlat(statement)
else
listOf(statement.transform(this, null))
}
}
override fun visitContainerExpression(expression: IrContainerExpression): IrExpression {
visitStatementContainer(expression)
return expression
}
override fun visitBlockBody(body: IrBlockBody): IrBody {
visitStatementContainer(body)
return body
}
override fun visitGetValue(expression: IrGetValue): IrExpression {
valueMap[expression.symbol]?.let {
return IrGetValueImpl(
expression.startOffset, expression.endOffset,
it.type, it.symbol, expression.origin
)
}
return super.visitGetValue(expression)
}
override fun visitSetVariable(expression: IrSetVariable): IrExpression {
valueMap[expression.symbol]?.let {
return IrSetVariableImpl(
expression.startOffset, expression.endOffset,
it.type, it.symbol as IrVariableSymbol, expression.origin
).apply {
value = expression.value.transform(this@JvmInlineClassLowering, null)
}
}
return super.visitSetVariable(expression)
}
private fun buildPrimaryInlineClassConstructor(irClass: IrClass, irConstructor: IrConstructor) {
// Add the default primary constructor
val primaryConstructor = irClass.addConstructor {
updateFrom(irConstructor)
visibility = Visibilities.PRIVATE
origin = JvmLoweredDeclarationOrigin.SYNTHETIC_INLINE_CLASS_MEMBER
returnType = irConstructor.returnType
}.apply {
copyParameterDeclarationsFrom(irConstructor)
body = context.createIrBuilder(this.symbol).irBlockBody(this) {
+irDelegatingConstructorCall(context.irBuiltIns.anyClass.owner.constructors.single())
+irSetField(
irGet(irClass.thisReceiver!!),
getInlineClassBackingField(irClass),
irGet(this@apply.valueParameters[0])
)
}
}
// Add a static bridge method to the primary constructor.
// This is a placeholder for null-checks and default arguments.
val function = manager.getReplacementFunction(irConstructor)!!.function
with(context.createIrBuilder(function.symbol)) {
val argument = function.valueParameters[0]
function.body = irExprBody(
coerceInlineClasses(irGet(argument), argument.type, function.returnType)
)
}
irClass.declarations += function
}
private fun buildBoxFunction(irClass: IrClass) {
val function = manager.getBoxFunction(irClass)
with(context.createIrBuilder(function.symbol)) {
function.body = irExprBody(
irCall(irClass.primaryConstructor!!.symbol).apply {
passTypeArgumentsFrom(function)
putValueArgument(0, irGet(function.valueParameters[0]))
}
)
}
irClass.declarations += function
}
private fun buildUnboxFunction(irClass: IrClass) {
val function = manager.getUnboxFunction(irClass)
val builder = context.createIrBuilder(function.symbol)
val field = getInlineClassBackingField(irClass)
function.body = builder.irBlockBody {
val thisVal = irGet(function.dispatchReceiverParameter!!)
+irReturn(irGetField(thisVal, field))
}
irClass.declarations += function
}
}
private val IrBody.contents: List<IrStatement>
get() = when (this) {
is IrBlockBody -> statements
is IrExpressionBody -> listOf(expression)
is IrSyntheticBody -> error("Synthetic body contains no statements $this")
else -> throw IllegalStateException()
}
@@ -0,0 +1,107 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.jvm.lower.inlineclasses
import org.jetbrains.kotlin.backend.common.ir.primaryConstructor
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.codegen.state.md5base64
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.isPrimitiveType
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.Name
/**
* Replace inline classes by their underlying types.
*/
fun IrType.unboxInlineClass() = InlineClassAbi.unboxType(this) ?: this
object InlineClassAbi {
/**
* Unwraps inline class types to their underlying representation.
* Returns null if the type cannot be unboxed.
*/
internal fun unboxType(type: IrType): IrType? {
val klass = type.classOrNull?.owner ?: return null
if (!klass.isInline) return null
val underlyingType = getUnderlyingType(klass).unboxInlineClass()
if (!type.isNullable())
return underlyingType
if (underlyingType.isNullable() || underlyingType.isPrimitiveType())
return null
return underlyingType.makeNullable()
}
/**
* Get the underlying type of an inline class based on the single argument to its
* primary constructor. This is what the current jvm backend does.
*
* Looking for a backing field does not work for unsigned types, which don't
* contain a field.
*/
fun getUnderlyingType(irClass: IrClass): IrType {
require(irClass.isInline)
return irClass.primaryConstructor!!.valueParameters[0].type
}
/**
* Returns a mangled name for a function taking inline class arguments
* to avoid clashes between overloaded methods.
*/
fun mangledNameFor(irFunction: IrFunction): Name {
val base = when {
irFunction is IrConstructor ->
"constructor"
irFunction.isGetter ->
JvmAbi.getterName(irFunction.propertyName.asString())
irFunction.isSetter ->
JvmAbi.setterName(irFunction.propertyName.asString())
irFunction.name.isSpecial ->
error("Unhandled special name in mangledNameFor: ${irFunction.name}")
else ->
irFunction.name.asString()
}
val suffix = when {
irFunction.fullValueParameterList.any { it.type.erasedUpperBound.isInline } ->
hashSuffix(irFunction)
(irFunction.parent as? IrClass)?.isInline == true -> "impl"
else -> return irFunction.name
}
return Name.identifier("$base-$suffix")
}
private val IrFunction.propertyName: Name
get() = (this as IrSimpleFunction).correspondingPropertySymbol!!.owner.name
private fun hashSuffix(irFunction: IrFunction) =
md5base64(irFunction.fullValueParameterList.joinToString { it.type.eraseToString() })
private fun IrType.eraseToString() = buildString {
append('L')
append(erasedUpperBound.fqNameWhenAvailable!!)
if (isNullable()) append('?')
append(';')
}
}
internal val IrFunction.fullValueParameterList: List<IrValueParameter>
get() = listOfNotNull(extensionReceiverParameter) + valueParameters
internal val IrClass.inlineClassFieldName: Name
get() = primaryConstructor!!.valueParameters.single().name
val IrFunction.isInlineClassFieldGetter: Boolean
get() = (parent as? IrClass)?.isInline == true && this is IrSimpleFunction && extensionReceiverParameter == null &&
correspondingPropertySymbol?.let { it.owner.getter == this && it.owner.name == parentAsClass.inlineClassFieldName } == true
val IrFunction.isPrimaryInlineClassConstructor: Boolean
get() = this is IrConstructor && isPrimary && constructedClass.isInline
@@ -0,0 +1,165 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.jvm.lower.inlineclasses
import org.jetbrains.kotlin.backend.common.ir.copyTo
import org.jetbrains.kotlin.backend.common.ir.copyTypeParameters
import org.jetbrains.kotlin.backend.common.ir.copyTypeParametersFrom
import org.jetbrains.kotlin.backend.common.ir.createDispatchReceiverParameter
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.InlineClassAbi.mangledNameFor
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol
import org.jetbrains.kotlin.ir.util.constructedClass
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.explicitParameters
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.storage.LockBasedStorageManager
class IrReplacementFunction(
val function: IrFunction,
val valueParameterMap: Map<IrValueParameterSymbol, IrValueParameter>
)
/**
* Keeps track of replacement functions and inline class box/unbox functions.
*/
class MemoizedInlineClassReplacements {
private val storageManager = LockBasedStorageManager("inline-class-replacements")
/**
* Get a replacement for a function or a constructor.
*/
val getReplacementFunction: (IrFunction) -> IrReplacementFunction? =
storageManager.createMemoizedFunctionWithNullableValues {
when {
!it.hasInlineClassParameters || it.isSyntheticInlineClassMember || it.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA -> null
it.hasMethodReplacement -> createMethodReplacement(it)
it.hasStaticReplacement -> createStaticReplacement(it)
else -> null
}
}
private val IrFunction.hasInlineClassParameters: Boolean
get() = explicitParameters.any { it.type.erasedUpperBound.isInline } || (this is IrConstructor && constructedClass.isInline)
private val IrFunction.hasStaticReplacement: Boolean
get() = origin != IrDeclarationOrigin.FAKE_OVERRIDE &&
(this is IrSimpleFunction || this is IrConstructor && constructedClass.isInline)
private val IrFunction.hasMethodReplacement: Boolean
get() = dispatchReceiverParameter != null && this is IrSimpleFunction && (parent as? IrClass)?.isInline != true
private val IrFunction.isSyntheticInlineClassMember: Boolean
get() = origin == JvmLoweredDeclarationOrigin.SYNTHETIC_INLINE_CLASS_MEMBER || isInlineClassFieldGetter
/**
* Get the box function for an inline class. Concretely, this is a synthetic
* static function named "box-impl" which takes an unboxed value and returns
* a boxed value.
*/
val getBoxFunction: (IrClass) -> IrSimpleFunction =
storageManager.createMemoizedFunction { irClass ->
require(irClass.isInline)
buildFun {
name = Name.identifier("box-impl")
origin = JvmLoweredDeclarationOrigin.SYNTHETIC_INLINE_CLASS_MEMBER
returnType = irClass.defaultType
}.apply {
parent = irClass
copyTypeParametersFrom(irClass)
addValueParameter {
name = Name.identifier("v")
type = InlineClassAbi.getUnderlyingType(irClass)
}
}
}
/**
* Get the unbox function for an inline class. Concretely, this is a synthetic
* member function named "unbox-impl" which returns an unboxed result.
*/
val getUnboxFunction: (IrClass) -> IrSimpleFunction =
storageManager.createMemoizedFunction { irClass ->
require(irClass.isInline)
buildFun {
name = Name.identifier("unbox-impl")
origin = JvmLoweredDeclarationOrigin.SYNTHETIC_INLINE_CLASS_MEMBER
returnType = InlineClassAbi.getUnderlyingType(irClass)
}.apply {
parent = irClass
createDispatchReceiverParameter()
}
}
private fun createMethodReplacement(function: IrFunction): IrReplacementFunction? {
require(function.dispatchReceiverParameter != null && function is IrSimpleFunction)
val overrides = function.overriddenSymbols.mapNotNull {
getReplacementFunction(it.owner)?.function?.symbol as? IrSimpleFunctionSymbol
}
if (function.origin == IrDeclarationOrigin.FAKE_OVERRIDE && overrides.isEmpty())
return null
val parameterMap = mutableMapOf<IrValueParameterSymbol, IrValueParameter>()
val replacement = buildReplacement(function) {
annotations += function.annotations
metadata = function.metadata
overriddenSymbols.addAll(overrides)
for ((index, parameter) in function.explicitParameters.withIndex()) {
val name = if (parameter == function.extensionReceiverParameter) Name.identifier("\$receiver") else parameter.name
val newParameter = parameter.copyTo(this, index = index - 1, name = name)
if (parameter == function.dispatchReceiverParameter)
dispatchReceiverParameter = newParameter
else
valueParameters.add(newParameter)
parameterMap[parameter.symbol] = newParameter
}
}
return IrReplacementFunction(replacement, parameterMap)
}
private fun createStaticReplacement(function: IrFunction): IrReplacementFunction {
val parameterMap = mutableMapOf<IrValueParameterSymbol, IrValueParameter>()
val replacement = buildReplacement(function) {
if (function !is IrSimpleFunction || function.overriddenSymbols.isEmpty())
metadata = function.metadata
for ((index, parameter) in function.explicitParameters.withIndex()) {
val name = when (parameter) {
function.dispatchReceiverParameter -> Name.identifier("\$this")
function.extensionReceiverParameter -> Name.identifier("\$receiver")
else -> parameter.name
}
val newParameter = parameter.copyTo(this, index = index, name = name)
valueParameters.add(newParameter)
parameterMap[parameter.symbol] = newParameter
}
}
return IrReplacementFunction(replacement, parameterMap)
}
private fun buildReplacement(function: IrFunction, body: IrFunctionImpl.() -> Unit) =
buildFun {
updateFrom(function)
name = mangledNameFor(function)
returnType = function.returnType
}.apply {
parent = function.parent
if (function is IrConstructor) {
copyTypeParameters(function.constructedClass.typeParameters + function.typeParameters)
} else {
copyTypeParametersFrom(function)
}
body()
}
}
@@ -7,10 +7,14 @@ package org.jetbrains.kotlin.ir.expressions
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.declarations.IrTypeParameter
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.types.toKotlinType
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.types.KotlinType
interface IrMemberAccessExpression : IrExpression {
@@ -102,3 +106,12 @@ inline fun <T : IrMemberAccessExpression> T.mapValueParametersIndexed(transform:
}
}
fun IrMemberAccessExpression.putArgument(callee: IrFunction, parameter: IrValueParameter, argument: IrExpression) =
when (parameter) {
callee.dispatchReceiverParameter -> dispatchReceiver = argument
callee.extensionReceiverParameter -> extensionReceiver = argument
else -> putValueArgument(parameter.index, argument)
}
fun IrFunctionAccessExpression.putArgument(parameter: IrValueParameter, argument: IrExpression) =
putArgument(symbol.owner, parameter, argument)