From 9c957edcea66fc7f6502408e87b0a157fa3c41bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Sch=C3=A4fer?= Date: Fri, 10 May 2019 12:18:28 +0200 Subject: [PATCH] Implement inline classes in the JVM_IR backend --- .../org/jetbrains/kotlin/codegen/FrameMap.kt | 4 +- .../jetbrains/kotlin/codegen/StackValue.java | 2 +- .../codegen/state/inlineClassManglingUtils.kt | 2 +- .../kotlin/backend/common/ir/IrUtils.kt | 32 +- .../declarations/declarationBuilders.kt | 4 +- .../kotlin/backend/jvm/DeclarationOrigins.kt | 1 + .../jetbrains/kotlin/backend/jvm/JvmLower.kt | 1 + .../kotlin/backend/jvm/JvmSymbols.kt | 13 + .../backend/jvm/codegen/ClassCodegen.kt | 2 +- .../backend/jvm/codegen/ExpressionCodegen.kt | 178 ++++---- .../backend/jvm/codegen/FunctionCodegen.kt | 9 +- .../backend/jvm/codegen/IrCallGenerator.kt | 6 +- .../backend/jvm/codegen/IrInlineCodegen.kt | 15 +- .../backend/jvm/codegen/IrTypeMapper.kt | 9 +- .../backend/jvm/codegen/PromisedValue.kt | 85 +++- .../backend/jvm/codegen/SwitchGenerator.kt | 4 +- .../backend/jvm/codegen/irCodegenUtils.kt | 16 +- .../kotlin/backend/jvm/intrinsics/AndAnd.kt | 2 +- .../kotlin/backend/jvm/intrinsics/Clone.kt | 2 +- .../backend/jvm/intrinsics/CompareTo.kt | 7 +- .../kotlin/backend/jvm/intrinsics/Equals.kt | 17 +- .../kotlin/backend/jvm/intrinsics/HashCode.kt | 4 +- .../backend/jvm/intrinsics/IntrinsicMethod.kt | 2 +- .../backend/jvm/intrinsics/IrEnumValueOf.kt | 2 +- .../jvm/intrinsics/IrIntrinsicFunction.kt | 2 +- .../jvm/intrinsics/IrIntrinsicMethods.kt | 3 +- .../jvm/intrinsics/JavaClassProperty.kt | 29 +- .../kotlin/backend/jvm/intrinsics/NewArray.kt | 2 +- .../kotlin/backend/jvm/intrinsics/Not.kt | 2 +- .../kotlin/backend/jvm/intrinsics/OrOr.kt | 2 +- .../backend/jvm/intrinsics/UnsafeCoerce.kt | 29 ++ .../jvm/lower/CallableReferenceLowering.kt | 24 +- .../jvm/lower/JvmInlineClassLowering.kt | 389 ++++++++++++++++++ .../jvm/lower/inlineclasses/InlineClassAbi.kt | 107 +++++ .../MemoizedInlineClassReplacements.kt | 165 ++++++++ .../expressions/IrMemberAccessExpression.kt | 13 + 36 files changed, 1026 insertions(+), 160 deletions(-) create mode 100644 compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/UnsafeCoerce.kt create mode 100644 compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt create mode 100644 compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/InlineClassAbi.kt create mode 100644 compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/MemoizedInlineClassReplacements.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/FrameMap.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/FrameMap.kt index 1d93e1f92ac..3fc3899d6dd 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/FrameMap.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/FrameMap.kt @@ -31,7 +31,7 @@ open class FrameMapBase { 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 { return index } - fun leave(descriptor: T): Int { + open fun leave(descriptor: T): Int { val size = myVarSizes.get(descriptor) currentSize -= size myVarSizes.remove(descriptor) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java index 7951b193f66..8d6c8af5d70 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java @@ -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); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/inlineClassManglingUtils.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/inlineClassManglingUtils.kt index f79ce74a6e9..79a96b9f14b 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/inlineClassManglingUtils.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/inlineClassManglingUtils.kt @@ -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) diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/IrUtils.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/IrUtils.kt index 1302d0a4cbf..08c233c7723 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/IrUtils.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/IrUtils.kt @@ -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, 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) diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/ir/builders/declarations/declarationBuilders.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/ir/builders/declarations/declarationBuilders.kt index c59cffe6a21..c6f5cb47711 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/ir/builders/declarations/declarationBuilders.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/ir/builders/declarations/declarationBuilders.kt @@ -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() diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt index b89ceec308f..0e376e64356 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt @@ -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 { diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt index 6e8903b3d8b..3afce5fdfbe 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt @@ -116,6 +116,7 @@ val jvmPhases = namedIrFilePhase( tailrecPhase then jvmDefaultConstructorPhase then + jvmInlineClassPhase then defaultArgumentStubPhase then interfacePhase then diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt index 49913de29a5..f0424ba5aba 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt @@ -257,4 +257,17 @@ class JvmSymbols( val getOrCreateKotlinClasses: IrSimpleFunctionSymbol = reflection.functions.single { it.owner.name.asString() == "getOrCreateKotlinClasses" } + + val unsafeCoerceIntrinsic = + buildFun { + name = Name.special("") + 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 } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt index eb4dd328f94..cccd587ceef 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt @@ -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 diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index eb9212b94b0..b8db54c0f49 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -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 . - 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 visitConst(expression: IrConst, 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, "", "(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 { 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), diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt index c21a3c246c0..4f865feec1c 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt @@ -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) { diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrCallGenerator.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrCallGenerator.kt index 501e3a0682f..abf9fdd16a4 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrCallGenerator.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrCallGenerator.kt @@ -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) { diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt index 5a78e5957c2..eb45d3f72ca 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt @@ -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().single() val boundReceiver = argumentExpression.statements.filterIsInstance().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) } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrTypeMapper.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrTypeMapper.kt index 784e5a25052..316140df866 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrTypeMapper.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrTypeMapper.kt @@ -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, sw: JvmSignatureWriter) = kotlinTypeMapper.writeFormalTypeParameters(irParameters.map { it.descriptor }, sw) -} \ No newline at end of file + + fun boxType(irType: IrType) = + AsmUtil.boxType(mapType(irType), irType.toKotlinType(), kotlinTypeMapper) +} diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/PromisedValue.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/PromisedValue.kt index 92c333844d8..08ae50eeb19 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/PromisedValue.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/PromisedValue.kt @@ -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) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/SwitchGenerator.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/SwitchGenerator.kt index 651245fbe62..54f505321b7 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/SwitchGenerator.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/SwitchGenerator.kt @@ -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 } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/irCodegenUtils.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/irCodegenUtils.kt index 1e3dfe29a12..e619dbf336d 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/irCodegenUtils.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/irCodegenUtils.kt @@ -43,7 +43,21 @@ import org.jetbrains.org.objectweb.asm.Type import java.util.ArrayList import java.util.LinkedHashSet -class IrFrameMap : FrameMapBase() +class IrFrameMap : FrameMapBase() { + private val typeMap = mutableMapOf() + + 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) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/AndAnd.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/AndAnd.kt index 1e35328fab4..62ef11261f3 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/AndAnd.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/AndAnd.kt @@ -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) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Clone.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Clone.kt index 68474247221..5db8b1cde43 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Clone.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Clone.kt @@ -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) } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt index 224b9043a1f..30487b3d849 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt @@ -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) } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt index dcaff7a22ae..d329bc35909 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt @@ -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 -> diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/HashCode.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/HashCode.kt index 3bd08a5ecb1..1b190b0f364 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/HashCode.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/HashCode.kt @@ -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) } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IntrinsicMethod.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IntrinsicMethod.kt index 060ac3fa3ed..b07663d9434 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IntrinsicMethod.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IntrinsicMethod.kt @@ -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) } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrEnumValueOf.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrEnumValueOf.kt index 3e7f6a669f1..04313f06a10 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrEnumValueOf.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrEnumValueOf.kt @@ -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) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicFunction.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicFunction.kt index e5761cf3f98..070c9bcb6ee 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicFunction.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicFunction.kt @@ -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 { diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt index 91e85bc8db9..dbfb54639b3 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt @@ -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) + diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/JavaClassProperty.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/JavaClassProperty.kt index 1445b2b6f14..61ac15e29af 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/JavaClassProperty.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/JavaClassProperty.kt @@ -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 } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/NewArray.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/NewArray.kt index cc6428ffc61..3706c8c1dd0 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/NewArray.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/NewArray.kt @@ -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 } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Not.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Not.kt index ed8ea0148ec..90ae463d504 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Not.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Not.kt @@ -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) } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/OrOr.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/OrOr.kt index caca8ffc378..0912ae4e95a 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/OrOr.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/OrOr.kt @@ -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() diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/UnsafeCoerce.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/UnsafeCoerce.kt new file mode 100644 index 00000000000..1fd57c71e73 --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/UnsafeCoerce.kt @@ -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(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() + } + } +} diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CallableReferenceLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CallableReferenceLowering.kt index 9f05da5b1d6..190b683441b 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CallableReferenceLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CallableReferenceLowering.kt @@ -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 are used" } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt new file mode 100644 index 00000000000..0ee54ff8538 --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt @@ -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() + + 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? { + 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 { + 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 { + 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()?.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 + get() = when (this) { + is IrBlockBody -> statements + is IrExpressionBody -> listOf(expression) + is IrSyntheticBody -> error("Synthetic body contains no statements $this") + else -> throw IllegalStateException() + } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/InlineClassAbi.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/InlineClassAbi.kt new file mode 100644 index 00000000000..7a698d6f400 --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/InlineClassAbi.kt @@ -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 + 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 diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/MemoizedInlineClassReplacements.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/MemoizedInlineClassReplacements.kt new file mode 100644 index 00000000000..20c75b74791 --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/MemoizedInlineClassReplacements.kt @@ -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 +) + +/** + * 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() + 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() + 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() + } +} diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/IrMemberAccessExpression.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/IrMemberAccessExpression.kt index f74476e6f50..c4382f3e64d 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/IrMemberAccessExpression.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/IrMemberAccessExpression.kt @@ -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.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)