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 f10b9168080..7073ec3aa48 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 @@ -191,6 +191,7 @@ private val jvmFilePhases = toArrayPhase then jvmBuiltinOptimizationLoweringPhase then additionalClassAnnotationPhase then + typeOperatorLowering then recordNamesForKotlinTypeMapperPhase 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 0e3ee175361..99a837ab740 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 @@ -19,6 +19,7 @@ import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin import org.jetbrains.kotlin.ir.declarations.IrPackageFragment import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.symbols.impl.IrExternalPackageFragmentSymbolImpl @@ -41,32 +42,47 @@ class JvmSymbols( firMode: Boolean ) : Symbols(context, symbolTable) { private val storageManager = LockBasedStorageManager(this::class.java.simpleName) - - private val irBuiltIns = context.irBuiltIns - - override val ThrowNullPointerException: IrSimpleFunctionSymbol - get() = error("Unused in JVM IR") - - override val ThrowNoWhenBranchMatchedException: IrSimpleFunctionSymbol - get() = error("Unused in JVM IR") - - override val ThrowTypeCastException: IrSimpleFunctionSymbol - get() = error("Unused in JVM IR") - - private fun createPackage(fqName: FqName): IrPackageFragment = - IrExternalPackageFragmentImpl(IrExternalPackageFragmentSymbolImpl(EmptyPackageFragmentDescriptor(context.state.module, fqName))) - + private val kotlinPackage: IrPackageFragment = createPackage(FqName("kotlin")) private val kotlinJvmPackage: IrPackageFragment = createPackage(FqName("kotlin.jvm")) private val kotlinJvmInternalPackage: IrPackageFragment = createPackage(FqName("kotlin.jvm.internal")) private val kotlinJvmFunctionsPackage: IrPackageFragment = createPackage(FqName("kotlin.jvm.functions")) private val javaLangPackage: IrPackageFragment = createPackage(FqName("java.lang")) + private val irBuiltIns = context.irBuiltIns + + private val nullPointerExceptionClass: IrClassSymbol = + createClass(FqName("java.lang.NullPointerException")) { klass -> + klass.addConstructor().apply { + addValueParameter("message", irBuiltIns.stringType) + } + }.symbol + + override val ThrowNullPointerException: IrFunctionSymbol = + nullPointerExceptionClass.constructors.single() + + override val ThrowNoWhenBranchMatchedException: IrSimpleFunctionSymbol + get() = error("Unused in JVM IR") + + private val typeCastExceptionClass: IrClassSymbol = + createClass(FqName("kotlin.TypeCastException")) { klass -> + klass.addConstructor().apply { + addValueParameter("message", irBuiltIns.stringType) + } + }.symbol + + override val ThrowTypeCastException: IrFunctionSymbol = + typeCastExceptionClass.constructors.single() + + private fun createPackage(fqName: FqName): IrPackageFragment = + IrExternalPackageFragmentImpl(IrExternalPackageFragmentSymbolImpl(EmptyPackageFragmentDescriptor(context.state.module, fqName))) + private fun createClass(fqName: FqName, classKind: ClassKind = ClassKind.CLASS, block: (IrClass) -> Unit): IrClass = buildClass { name = fqName.shortName() kind = classKind }.apply { parent = when (fqName.parent().asString()) { + "kotlin" -> kotlinPackage "kotlin.jvm.internal" -> kotlinJvmInternalPackage "kotlin.jvm.functions" -> kotlinJvmFunctionsPackage "java.lang" -> javaLangPackage @@ -82,8 +98,22 @@ class JvmSymbols( klass.addFunction("throwUninitializedPropertyAccessException", irBuiltIns.unitType, isStatic = true).apply { addValueParameter("propertyName", irBuiltIns.stringType) } + klass.addFunction("checkExpressionValueIsNotNull", irBuiltIns.unitType, isStatic = true).apply { + addValueParameter("value", irBuiltIns.anyNType) + addValueParameter("expression", irBuiltIns.stringType) + } + klass.addFunction("checkNotNullExpressionValue", irBuiltIns.unitType, isStatic = true).apply { + addValueParameter("value", irBuiltIns.anyNType) + addValueParameter("expression", irBuiltIns.stringType) + } }.symbol + val checkExpressionValueIsNotNull: IrSimpleFunctionSymbol = + intrinsicsClass.functions.single { it.owner.name.asString() == "checkExpressionValueIsNotNull" } + + val checkNotNullExpressionValue: IrSimpleFunctionSymbol = + intrinsicsClass.functions.single { it.owner.name.asString() == "checkNotNullExpressionValue" } + override val ThrowUninitializedPropertyAccessException: IrSimpleFunctionSymbol = intrinsicsClass.functions.single { it.owner.name.asString() == "throwUninitializedPropertyAccessException" } 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 5c4a30ee61a..25d30a4da9c 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 @@ -9,7 +9,6 @@ import org.jetbrains.kotlin.backend.common.lower.BOUND_RECEIVER_PARAMETER import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods import org.jetbrains.kotlin.backend.jvm.intrinsics.JavaClassProperty -import org.jetbrains.kotlin.backend.jvm.intrinsics.Not import org.jetbrains.kotlin.backend.jvm.lower.MultifileFacadeFileEntry import org.jetbrains.kotlin.backend.jvm.lower.constantValue import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass @@ -20,6 +19,7 @@ import org.jetbrains.kotlin.codegen.inline.* import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.Companion.putNeedClassReificationMarker import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.OperationKind.AS import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.OperationKind.SAFE_AS +import org.jetbrains.kotlin.codegen.intrinsics.TypeIntrinsics import org.jetbrains.kotlin.codegen.pseudoInsns.fakeAlwaysFalseIfeq import org.jetbrains.kotlin.codegen.pseudoInsns.fixStackAndJump import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter @@ -637,15 +637,8 @@ class ExpressionCodegen( val typeOperand = expression.typeOperand val kotlinType = typeOperand.toKotlinType() return when (expression.operator) { - IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> { - expression.argument.accept(this, data).discard() - expression.argument.markEndOfStatementIfNeeded() - defaultValue(expression.type) - } - - IrTypeOperator.IMPLICIT_CAST, IrTypeOperator.IMPLICIT_INTEGER_COERCION -> { + IrTypeOperator.IMPLICIT_CAST -> expression.argument.accept(this, data).coerce(expression.type) - } IrTypeOperator.CAST, IrTypeOperator.SAFE_CAST -> { val result = expression.argument.accept(this, data) @@ -654,44 +647,26 @@ class ExpressionCodegen( val boxedRightType = typeMapper.boxType(typeOperand) if (typeOperand.isReifiedTypeParameter) { - putReifiedOperationMarkerIfTypeIsReifiedParameter( - typeOperand, if (IrTypeOperator.SAFE_CAST == expression.operator) SAFE_AS else AS, mv, this - ) + val operationKind = if (expression.operator == IrTypeOperator.CAST) AS else SAFE_AS + putReifiedOperationMarkerIfTypeIsReifiedParameter(typeOperand, operationKind, mv, this) v.checkcast(boxedRightType) } else { - generateAsCast( - mv, kotlinType, boxedRightType, expression.operator == IrTypeOperator.SAFE_CAST, state.languageVersionSettings - ) + assert(expression.operator == IrTypeOperator.CAST) { "IrTypeOperator.SAFE_CAST should have been lowered." } + TypeIntrinsics.checkcast(mv, kotlinType, boxedRightType, false) } MaterialValue(this, boxedRightType, expression.type).coerce(expression.type) } - IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF -> { - expression.argument.accept(this, data).coerce(OBJECT_TYPE, context.irBuiltIns.anyNType).materialize() + IrTypeOperator.INSTANCEOF -> { + expression.argument.accept(this, data).coerce(context.irBuiltIns.anyNType).materialize() val type = typeMapper.boxType(typeOperand) if (typeOperand.isReifiedTypeParameter) { putReifiedOperationMarkerIfTypeIsReifiedParameter(typeOperand, ReifiedTypeInliner.OperationKind.IS, mv, this) v.instanceOf(type) } else { - generateIsCheck(mv, kotlinType, type, state.languageVersionSettings.isReleaseCoroutines()) + TypeIntrinsics.instanceOf(mv, kotlinType, type, state.languageVersionSettings.isReleaseCoroutines()) } - // TODO remove this type operator, generate an intrinsic call around INSTANCEOF instead - if (IrTypeOperator.NOT_INSTANCEOF == expression.operator) - Not.BooleanNegation(expression.onStack.coerceToBoolean()) - else - expression.onStack - } - - IrTypeOperator.IMPLICIT_NOTNULL -> { - val value = expression.argument.accept(this, data).materialized - mv.dup() - mv.visitLdcInsn("TODO provide message for IMPLICIT_NOTNULL") /*TODO*/ - val methodName = - if (state.languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4) "checkNotNullExpressionValue" - else "checkExpressionValueIsNotNull" - mv.invokestatic(IrIntrinsicMethods.INTRINSICS_CLASS_NAME, methodName, "(Ljava/lang/Object;Ljava/lang/String;)V", false) - // Unbox primitives. - value.coerce(expression.type) + expression.onStack } else -> throw AssertionError("type operator ${expression.operator} should have been lowered") @@ -942,9 +917,7 @@ class ExpressionCodegen( override fun visitThrow(expression: IrThrow, data: BlockInfo): PromisedValue { expression.markLineNumber(startOffset = true) - expression.value.accept(this, data) - .coerce(AsmTypes.JAVA_THROWABLE_TYPE, context.irBuiltIns.throwableType) - .materialize() + expression.value.accept(this, data).materialize() mv.athrow() return immaterialUnitValue } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/TypeOperatorLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/TypeOperatorLowering.kt new file mode 100644 index 00000000000..17d774daa6c --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/TypeOperatorLowering.kt @@ -0,0 +1,194 @@ +/* + * 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.lower.IrBuildingTransformer +import org.jetbrains.kotlin.backend.common.lower.at +import org.jetbrains.kotlin.backend.common.lower.irNot +import org.jetbrains.kotlin.backend.common.lower.irThrow +import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase +import org.jetbrains.kotlin.backend.jvm.JvmBackendContext +import org.jetbrains.kotlin.backend.jvm.codegen.fileParent +import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound +import org.jetbrains.kotlin.config.ApiVersion +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.IrDeclaration +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin +import org.jetbrains.kotlin.ir.expressions.IrTypeOperator +import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall +import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl +import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.render +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid +import org.jetbrains.kotlin.ir.visitors.acceptVoid +import org.jetbrains.kotlin.utils.addToStdlib.safeAs + +// After this pass runs there are only four kinds of IrTypeOperatorCalls left: +// +// - IMPLICIT_CAST +// - SAFE_CAST with reified type parameters +// - INSTANCEOF with non-nullable type operand or reified type parameters +// - CAST with non-nullable argument, nullable type operand, or reified type parameters +// +// The latter two correspond to the instanceof/checkcast instructions on the JVM, except for +// the presence of reified type parameters. +internal val typeOperatorLowering = makeIrFilePhase( + ::TypeOperatorLowering, + name = "TypeOperatorLowering", + description = "Lower IrTypeOperatorCalls to (implicit) casts and instanceof checks" +) + +private class TypeOperatorLowering(private val context: JvmBackendContext) : FileLoweringPass, IrBuildingTransformer(context) { + override fun lower(irFile: IrFile) = irFile.transformChildrenVoid() + + private fun IrExpression.transformVoid() = transform(this@TypeOperatorLowering, null) + + private val IrType.isReifiedTypeParameter: Boolean + get() = classifierOrNull?.safeAs()?.owner?.isReified == true + + private fun lowerInstanceOf(argument: IrExpression, type: IrType) = with(builder) { + when { + type.isReifiedTypeParameter -> + irIs(argument, type) + argument.type.isNullable() && type.isNullable() -> { + irLetS(argument) { valueSymbol -> + context.oror( + irEqualsNull(irGet(valueSymbol.owner)), + irIs(irGet(valueSymbol.owner), type.makeNotNull()) + ) + } + } + else -> irIs(argument, type.makeNotNull()) + } + } + + private fun lowerCast(argument: IrExpression, type: IrType): IrExpression = when { + type.isReifiedTypeParameter -> + builder.irAs(argument, type) + argument.type.isNullable() && !type.isNullable() -> + with(builder) { + irLetS(argument) { valueSymbol -> + irIfNull( + type, + irGet(valueSymbol.owner), + irThrow(irCall(typeCastException).apply { + putValueArgument(0, irString("null cannot be cast to non-null type ${type.render()}")) + }), + lowerCast(irGet(valueSymbol.owner), type.makeNullable()) + ) + } + } + argument.type.isSubtypeOfClass(type.erasedUpperBound.symbol) -> + argument + else -> + builder.irAs(argument, type) + } + + override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression = with(builder) { + at(expression) + return when (expression.operator) { + IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> + irComposite(resultType = expression.type) { + +expression.argument.transformVoid() + // TODO: Don't generate these casts in the first place + if (!expression.argument.type.isSubtypeOf(expression.type.makeNullable(), context.irBuiltIns)) { + +IrCompositeImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, expression.type) + } + } + + // There is no difference between IMPLICIT_CAST and IMPLICIT_INTEGER_COERCION on JVM_IR. + // Instead, this is handled in StackValue.coerce. + IrTypeOperator.IMPLICIT_INTEGER_COERCION -> + irImplicitCast(expression.argument.transformVoid(), expression.typeOperand) + + IrTypeOperator.CAST -> + lowerCast(expression.argument.transformVoid(), expression.typeOperand) + + IrTypeOperator.SAFE_CAST -> + if (expression.typeOperand.isReifiedTypeParameter) { + expression.transformChildrenVoid() + expression + } else { + irLetS(expression.argument.transformVoid(), IrStatementOrigin.SAFE_CALL) { valueSymbol -> + irIfThenElse( + expression.type, + lowerInstanceOf(irGet(valueSymbol.owner), expression.typeOperand.makeNotNull()), + irGet(valueSymbol.owner), + irNull(expression.type) + ) + } + } + + IrTypeOperator.INSTANCEOF -> + lowerInstanceOf(expression.argument.transformVoid(), expression.typeOperand) + + IrTypeOperator.NOT_INSTANCEOF -> + irNot(lowerInstanceOf(expression.argument.transformVoid(), expression.typeOperand)) + + IrTypeOperator.IMPLICIT_NOTNULL -> { + val (startOffset, endOffset) = expression.extents() + val source = sourceViewFor(parent as IrDeclaration).subSequence(startOffset, endOffset).toString() + + irLetS(expression.argument.transformVoid()) { valueSymbol -> + irComposite(resultType = expression.type) { + +irCall(checkExpressionValueIsNotNull).apply { + putValueArgument(0, irGet(valueSymbol.owner)) + putValueArgument(1, irString(source)) + } + +irGet(valueSymbol.owner) + } + } + } + + else -> { + expression.transformChildrenVoid() + expression + } + } + } + + private fun IrElement.extents(): Pair { + var startOffset = UNDEFINED_OFFSET + var endOffset = UNDEFINED_OFFSET + acceptVoid(object : IrElementVisitorVoid { + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + if (startOffset == UNDEFINED_OFFSET || element.startOffset != UNDEFINED_OFFSET && element.startOffset < startOffset) + startOffset = element.startOffset + if (endOffset == UNDEFINED_OFFSET || element.endOffset != UNDEFINED_OFFSET && endOffset < element.endOffset) + endOffset = element.endOffset + } + }) + return startOffset to endOffset + } + + private fun sourceViewFor(declaration: IrDeclaration) = + context.psiSourceManager.getKtFile(declaration.fileParent)!!.viewProvider.contents + + // Since Kotlin 1.4 we throw NullPointerExceptions instead of more specialized exception classes. + private val useNullPointerExceptions: Boolean + get() = context.state.languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4 + + private val typeCastException: IrFunctionSymbol = + if (useNullPointerExceptions) + context.ir.symbols.ThrowNullPointerException + else + context.ir.symbols.ThrowTypeCastException + + private val checkExpressionValueIsNotNull: IrFunctionSymbol = + if (useNullPointerExceptions) + context.ir.symbols.checkNotNullExpressionValue + else + context.ir.symbols.checkExpressionValueIsNotNull +} diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt index ee2640b86f8..597c6fa93de 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt @@ -19,20 +19,6 @@ import org.jetbrains.kotlin.utils.addToStdlib.assertedCast val IrBuilderWithScope.parent get() = scope.getLocalDeclarationParent() -inline fun IrBuilderWithScope.irLet( - value: IrExpression, - origin: IrStatementOrigin? = null, - nameHint: String? = null, - body: (VariableDescriptor) -> IrExpression -): IrExpression { - val irTemporary = scope.createTemporaryVariable(value, nameHint) - val irResult = body(irTemporary.descriptor) - val irBlock = IrBlockImpl(startOffset, endOffset, irResult.type, origin) - irBlock.statements.add(irTemporary) - irBlock.statements.add(irResult) - return irBlock -} - inline fun IrBuilderWithScope.irLetS( value: IrExpression, origin: IrStatementOrigin? = null, @@ -185,13 +171,13 @@ fun IrBuilderWithScope.irEqeqeq(arg1: IrExpression, arg2: IrExpression) = context.eqeqeq(startOffset, endOffset, arg1, arg2) fun IrBuilderWithScope.irNull() = - IrConstImpl.constNull(startOffset, endOffset, context.irBuiltIns.nothingNType) + irNull(context.irBuiltIns.nothingNType) + +fun IrBuilderWithScope.irNull(irType: IrType) = + IrConstImpl.constNull(startOffset, endOffset, irType) fun IrBuilderWithScope.irEqualsNull(argument: IrExpression) = - primitiveOp2( - startOffset, endOffset, context.irBuiltIns.eqeqSymbol, context.irBuiltIns.booleanType, IrStatementOrigin.EQEQ, - argument, irNull() - ) + irEquals(argument, irNull()) fun IrBuilderWithScope.irEquals(arg1: IrExpression, arg2: IrExpression, origin: IrStatementOrigin = IrStatementOrigin.EQEQ) = primitiveOp2(