JVM IR: Implement CHECK_NOT_NULL as a lowering
This commit is contained in:
committed by
Alexander Udalov
parent
ea6c43dabd
commit
dcd72b06d8
+10
-5
@@ -142,9 +142,9 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
|
|||||||
|
|
||||||
private fun transformTrivialCheckNotNull(insn: AbstractInsnNode, nullability: Nullability) {
|
private fun transformTrivialCheckNotNull(insn: AbstractInsnNode, nullability: Nullability) {
|
||||||
if (nullability != Nullability.NOT_NULL) return
|
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 {
|
methodNode.instructions.run {
|
||||||
remove(dupInsn)
|
remove(previousInsn)
|
||||||
remove(insn)
|
remove(insn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,9 +184,10 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
insn.isCheckNotNull() -> {
|
insn.isCheckNotNull() -> {
|
||||||
val dupInsn = insn.previous ?: continue@insnLoop
|
val previous = insn.previous ?: continue@insnLoop
|
||||||
if (dupInsn.opcode != Opcodes.DUP) continue@insnLoop
|
val aLoadInsn = if (previous.opcode == Opcodes.DUP) {
|
||||||
val aLoadInsn = dupInsn.previous ?: continue@insnLoop
|
previous.previous ?: continue@insnLoop
|
||||||
|
} else previous
|
||||||
if (aLoadInsn.opcode != Opcodes.ALOAD) continue@insnLoop
|
if (aLoadInsn.opcode != Opcodes.ALOAD) continue@insnLoop
|
||||||
addDependentCheck(insn, aLoadInsn as VarInsnNode)
|
addDependentCheck(insn, aLoadInsn as VarInsnNode)
|
||||||
}
|
}
|
||||||
@@ -303,6 +304,10 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
|
|||||||
// INVOKESTATIC checkNotNull
|
// INVOKESTATIC checkNotNull
|
||||||
// <...> -- v is not null here (otherwise an exception was thrown)
|
// <...> -- 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
|
// ALOAD v
|
||||||
// LDC *
|
// LDC *
|
||||||
// INVOKESTATIC checkParameterIsNotNull
|
// INVOKESTATIC checkParameterIsNotNull
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ private val jvmFilePhases =
|
|||||||
annotationPhase then
|
annotationPhase then
|
||||||
varargPhase then
|
varargPhase then
|
||||||
arrayConstructorPhase then
|
arrayConstructorPhase then
|
||||||
|
checkNotNullPhase then
|
||||||
|
|
||||||
lateinitPhase then
|
lateinitPhase then
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,10 @@ class JvmSymbols(
|
|||||||
addValueParameter("self", irBuiltIns.stringType.makeNullable())
|
addValueParameter("self", irBuiltIns.stringType.makeNullable())
|
||||||
addValueParameter("other", irBuiltIns.anyNType)
|
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 =
|
val checkExpressionValueIsNotNull: IrSimpleFunctionSymbol =
|
||||||
@@ -128,6 +132,12 @@ class JvmSymbols(
|
|||||||
val checkNotNullExpressionValue: IrSimpleFunctionSymbol =
|
val checkNotNullExpressionValue: IrSimpleFunctionSymbol =
|
||||||
intrinsicsClass.functions.single { it.owner.name.asString() == "checkNotNullExpressionValue" }
|
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 =
|
override val ThrowUninitializedPropertyAccessException: IrSimpleFunctionSymbol =
|
||||||
intrinsicsClass.functions.single { it.owner.name.asString() == "throwUninitializedPropertyAccessException" }
|
intrinsicsClass.functions.single { it.owner.name.asString() == "throwUninitializedPropertyAccessException" }
|
||||||
|
|
||||||
|
|||||||
-37
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-1
@@ -87,7 +87,6 @@ class IrIntrinsicMethods(val irBuiltIns: IrBuiltIns, val symbols: JvmSymbols) {
|
|||||||
irBuiltIns.enumValueOfSymbol.toKey()!! to IrEnumValueOf,
|
irBuiltIns.enumValueOfSymbol.toKey()!! to IrEnumValueOf,
|
||||||
irBuiltIns.noWhenBranchMatchedExceptionSymbol.toKey()!! to IrNoWhenBranchMatchedException,
|
irBuiltIns.noWhenBranchMatchedExceptionSymbol.toKey()!! to IrNoWhenBranchMatchedException,
|
||||||
irBuiltIns.illegalArgumentExceptionSymbol.toKey()!! to IrIllegalArgumentException,
|
irBuiltIns.illegalArgumentExceptionSymbol.toKey()!! to IrIllegalArgumentException,
|
||||||
irBuiltIns.checkNotNullSymbol.toKey()!! to IrCheckNotNull,
|
|
||||||
irBuiltIns.andandSymbol.toKey()!! to AndAnd,
|
irBuiltIns.andandSymbol.toKey()!! to AndAnd,
|
||||||
irBuiltIns.ororSymbol.toKey()!! to OrOr,
|
irBuiltIns.ororSymbol.toKey()!! to OrOr,
|
||||||
symbols.unsafeCoerceIntrinsic.toKey()!! to UnsafeCoerce,
|
symbols.unsafeCoerceIntrinsic.toKey()!! to UnsafeCoerce,
|
||||||
|
|||||||
+57
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user