From dcd72b06d85b25b78b4885445f106a7d82574e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Sch=C3=A4fer?= Date: Tue, 29 Oct 2019 14:49:18 +0100 Subject: [PATCH] JVM IR: Implement CHECK_NOT_NULL as a lowering --- .../RedundantNullCheckMethodTransformer.kt | 15 +++-- .../jetbrains/kotlin/backend/jvm/JvmLower.kt | 1 + .../kotlin/backend/jvm/JvmSymbols.kt | 10 ++++ .../backend/jvm/intrinsics/IrCheckNotNull.kt | 37 ------------ .../jvm/intrinsics/IrIntrinsicMethods.kt | 1 - .../backend/jvm/lower/CheckNotNullLowering.kt | 57 +++++++++++++++++++ 6 files changed, 78 insertions(+), 43 deletions(-) delete mode 100644 compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrCheckNotNull.kt create mode 100644 compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CheckNotNullLowering.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt index c22d1c4603c..6a06c42f996 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt @@ -142,9 +142,9 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio private fun transformTrivialCheckNotNull(insn: AbstractInsnNode, nullability: Nullability) { if (nullability != Nullability.NOT_NULL) return - val dupInsn = insn.previous?.takeIf { it.opcode == Opcodes.DUP } ?: return + val previousInsn = insn.previous?.takeIf { it.opcode == Opcodes.DUP || it.opcode == Opcodes.ALOAD } ?: return methodNode.instructions.run { - remove(dupInsn) + remove(previousInsn) remove(insn) } } @@ -184,9 +184,10 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio } insn.isCheckNotNull() -> { - val dupInsn = insn.previous ?: continue@insnLoop - if (dupInsn.opcode != Opcodes.DUP) continue@insnLoop - val aLoadInsn = dupInsn.previous ?: continue@insnLoop + val previous = insn.previous ?: continue@insnLoop + val aLoadInsn = if (previous.opcode == Opcodes.DUP) { + previous.previous ?: continue@insnLoop + } else previous if (aLoadInsn.opcode != Opcodes.ALOAD) continue@insnLoop addDependentCheck(insn, aLoadInsn as VarInsnNode) } @@ -303,6 +304,10 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio // INVOKESTATIC checkNotNull // <...> -- v is not null here (otherwise an exception was thrown) + // ALOAD v + // INVOKESTATIC checkNotNull + // <...> -- v is not null here (otherwise an exception was thrown) + // ALOAD v // LDC * // INVOKESTATIC checkParameterIsNotNull 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 e78e684d367..a1f503c26c4 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 @@ -189,6 +189,7 @@ private val jvmFilePhases = annotationPhase then varargPhase then arrayConstructorPhase then + checkNotNullPhase then lateinitPhase 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 7c2a0886028..5248276365c 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 @@ -120,6 +120,10 @@ class JvmSymbols( addValueParameter("self", irBuiltIns.stringType.makeNullable()) addValueParameter("other", irBuiltIns.anyNType) } + klass.addFunction("checkNotNull", irBuiltIns.unitType, isStatic = true).apply { + addValueParameter("object", irBuiltIns.anyNType) + } + klass.addFunction("throwNpe", irBuiltIns.unitType, isStatic = true) } val checkExpressionValueIsNotNull: IrSimpleFunctionSymbol = @@ -128,6 +132,12 @@ class JvmSymbols( val checkNotNullExpressionValue: IrSimpleFunctionSymbol = intrinsicsClass.functions.single { it.owner.name.asString() == "checkNotNullExpressionValue" } + val checkNotNull: IrSimpleFunctionSymbol = + intrinsicsClass.functions.single { it.owner.name.asString() == "checkNotNull" } + + val throwNpe: IrSimpleFunctionSymbol = + intrinsicsClass.functions.single { it.owner.name.asString() == "throwNpe" } + 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/intrinsics/IrCheckNotNull.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrCheckNotNull.kt deleted file mode 100644 index 3556c93ca5d..00000000000 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrCheckNotNull.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.BlockInfo -import org.jetbrains.kotlin.backend.jvm.codegen.ExpressionCodegen -import org.jetbrains.kotlin.backend.jvm.codegen.PromisedValue -import org.jetbrains.kotlin.codegen.AsmUtil -import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods -import org.jetbrains.kotlin.config.ApiVersion -import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression -import org.jetbrains.org.objectweb.asm.Label - -object IrCheckNotNull : IntrinsicMethod() { - override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? { - val arg0 = expression.getValueArgument(0)!!.accept(codegen, data) - if (AsmUtil.isPrimitive(arg0.type)) return arg0 - - return object : PromisedValue(codegen, arg0.type, expression.type) { - override fun materialize() { - arg0.materialize() - mv.dup() - if (codegen.state.languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4) { - mv.invokestatic(IntrinsicMethods.INTRINSICS_CLASS_NAME, "checkNotNull", "(Ljava/lang/Object;)V", false) - } else { - val ifNonNullLabel = Label() - mv.ifnonnull(ifNonNullLabel) - mv.invokestatic(IntrinsicMethods.INTRINSICS_CLASS_NAME, "throwNpe", "()V", false) - mv.mark(ifNonNullLabel) - } - } - } - } -} 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 57b0f10b14c..5ddc001f7fb 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 @@ -87,7 +87,6 @@ class IrIntrinsicMethods(val irBuiltIns: IrBuiltIns, val symbols: JvmSymbols) { irBuiltIns.enumValueOfSymbol.toKey()!! to IrEnumValueOf, irBuiltIns.noWhenBranchMatchedExceptionSymbol.toKey()!! to IrNoWhenBranchMatchedException, irBuiltIns.illegalArgumentExceptionSymbol.toKey()!! to IrIllegalArgumentException, - irBuiltIns.checkNotNullSymbol.toKey()!! to IrCheckNotNull, irBuiltIns.andandSymbol.toKey()!! to AndAnd, irBuiltIns.ororSymbol.toKey()!! to OrOr, symbols.unsafeCoerceIntrinsic.toKey()!! to UnsafeCoerce, diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CheckNotNullLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CheckNotNullLowering.kt new file mode 100644 index 00000000000..718097b7b93 --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CheckNotNullLowering.kt @@ -0,0 +1,57 @@ +/* + * 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.IrElementTransformerVoidWithContext +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.irIfThen +import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase +import org.jetbrains.kotlin.backend.jvm.JvmBackendContext +import org.jetbrains.kotlin.config.ApiVersion +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrGetValue + +internal val checkNotNullPhase = makeIrFilePhase( + ::CheckNotNullLowering, + name = "CheckNotNullLowering", + description = "Lower calls to the CHECK_NOT_NULL intrinsic, which are generated by psi2ir for \"!!\" expressions." +) + +private class CheckNotNullLowering(private val backendContext: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() { + override fun lower(irFile: IrFile) = irFile.transformChildrenVoid() + + override fun visitCall(expression: IrCall): IrExpression { + if (expression.symbol != backendContext.irBuiltIns.checkNotNullSymbol) + return super.visitCall(expression) + + expression.transformChildrenVoid() + return backendContext.createIrBuilder(currentScope!!.scope.scopeOwnerSymbol, expression.startOffset, expression.endOffset).irBlock { + val valueArgument = expression.getValueArgument(0)!! + + // For a null-check on a variable we should not introduce a temporary, since null checks are + // optimized by RedundantNullCheckMethodTransformer, which remembers that the argument variable + // is non-nullable if this call succeeds. + val argument = if (valueArgument is IrGetValue) valueArgument.symbol.owner else irTemporary(valueArgument) + + // Starting with Kotlin 1.4 null-checks are lowered to calls to the "checkNotNull" intrinsic, which + // throws a NullPointerException on failure. Prior to Kotlin 1.4 we instead inline the null-check and + // call the "throwNpe" intrinsic on failure which throws a KotlinNullPointerException. + if (backendContext.state.languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4) { + +irCall(backendContext.ir.symbols.checkNotNull).apply { + putValueArgument(0, irGet(argument)) + } + } else { + +irIfThen(irEqualsNull(irGet(argument)), irCall(backendContext.ir.symbols.throwNpe)) + } + + +irImplicitCast(irGet(argument), expression.type) + } + } +}