JVM_IR KT-27427 use checkNotNull with message in checked casts

This would prevent extra branching in 'as <not-null-type>', which
usually would not be covered.
This commit is contained in:
Dmitry Petrov
2021-12-29 20:11:12 +03:00
committed by teamcity
parent 9132c9b2d0
commit 1e5fc1c3aa
11 changed files with 175 additions and 79 deletions
@@ -72,6 +72,7 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
val value = when { val value = when {
insn.isInstanceOfOrNullCheck() -> frame.top() insn.isInstanceOfOrNullCheck() -> frame.top()
insn.isCheckNotNull() -> frame.top() insn.isCheckNotNull() -> frame.top()
insn.isCheckNotNullWithMessage() -> frame.peek(1)
insn.isCheckExpressionValueIsNotNull() -> frame.peek(1) insn.isCheckExpressionValueIsNotNull() -> frame.peek(1)
else -> null else -> null
} as? StrictBasicValue ?: continue } as? StrictBasicValue ?: continue
@@ -88,6 +89,7 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
opcode == Opcodes.IFNONNULL || opcode == Opcodes.IFNONNULL ||
opcode == Opcodes.INSTANCEOF || opcode == Opcodes.INSTANCEOF ||
isCheckNotNull() || isCheckNotNull() ||
isCheckNotNullWithMessage() ||
isCheckExpressionValueIsNotNull() isCheckExpressionValueIsNotNull()
private fun transformTrivialChecks(nullabilityMap: Map<AbstractInsnNode, StrictBasicValue>) { private fun transformTrivialChecks(nullabilityMap: Map<AbstractInsnNode, StrictBasicValue>) {
@@ -99,11 +101,13 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
Opcodes.INSTANCEOF -> transformInstanceOf(insn as TypeInsnNode, nullability, value) Opcodes.INSTANCEOF -> transformInstanceOf(insn as TypeInsnNode, nullability, value)
Opcodes.INVOKESTATIC -> { Opcodes.INVOKESTATIC -> {
if (insn.isCheckNotNull()) { when {
transformTrivialCheckNotNull(insn, nullability) insn.isCheckNotNull() ->
} transformTrivialCheckNotNull(insn, nullability)
if (insn.isCheckExpressionValueIsNotNull()) { insn.isCheckNotNullWithMessage() ->
transformTrivialCheckExpressionValueIsNotNull(insn, nullability) transformTrivialCheckNotNullWithMessage(insn, nullability)
insn.isCheckExpressionValueIsNotNull() ->
transformTrivialCheckExpressionValueIsNotNull(insn, nullability)
} }
} }
} }
@@ -150,6 +154,17 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
} }
} }
private fun transformTrivialCheckNotNullWithMessage(insn: AbstractInsnNode, nullability: Nullability) {
if (nullability != Nullability.NOT_NULL) return
val ldcInsn = insn.previous?.takeIf { it.opcode == Opcodes.LDC } ?: return
val previousInsn = ldcInsn.previous?.takeIf { it.opcode == Opcodes.DUP || it.opcode == Opcodes.ALOAD } ?: return
methodNode.instructions.run {
remove(previousInsn)
remove(ldcInsn)
remove(insn)
}
}
private fun transformTrivialCheckExpressionValueIsNotNull(insn: AbstractInsnNode, nullability: Nullability) { private fun transformTrivialCheckExpressionValueIsNotNull(insn: AbstractInsnNode, nullability: Nullability) {
if (nullability != Nullability.NOT_NULL) return if (nullability != Nullability.NOT_NULL) return
val ldcInsn = insn.previous?.takeIf { it.opcode == Opcodes.LDC } ?: return val ldcInsn = insn.previous?.takeIf { it.opcode == Opcodes.LDC } ?: return
@@ -185,12 +200,14 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
} }
insn.isCheckNotNull() -> { insn.isCheckNotNull() -> {
val previous = insn.previous ?: continue val checkedValueInsn = insn.previous ?: continue
val aLoadInsn = if (previous.opcode == Opcodes.DUP) { addDependentCheckForCheckNotNull(insn, checkedValueInsn)
previous.previous ?: continue }
} else previous
if (aLoadInsn.opcode != Opcodes.ALOAD) continue insn.isCheckNotNullWithMessage() -> {
addDependentCheck(insn, aLoadInsn as VarInsnNode) val ldcInsn = insn.previous?.takeIf { it.opcode == Opcodes.LDC } ?: continue
val checkedValueInsn = ldcInsn.previous ?: continue
addDependentCheckForCheckNotNull(insn, checkedValueInsn)
} }
insn.isCheckParameterIsNotNull() -> { insn.isCheckParameterIsNotNull() -> {
@@ -221,6 +238,16 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
} }
} }
private fun addDependentCheckForCheckNotNull(insn: AbstractInsnNode, checkedValueInsn: AbstractInsnNode) {
val aLoadInsn = if (checkedValueInsn.opcode == Opcodes.DUP) {
checkedValueInsn.previous ?: return
} else {
checkedValueInsn
}
if (aLoadInsn.opcode != Opcodes.ALOAD) return
addDependentCheck(insn, aLoadInsn as VarInsnNode)
}
private fun addDependentCheck(insn: AbstractInsnNode, aLoadInsn: VarInsnNode) { private fun addDependentCheck(insn: AbstractInsnNode, aLoadInsn: VarInsnNode) {
checksDependingOnVariable.getOrPut(aLoadInsn.`var`) { checksDependingOnVariable.getOrPut(aLoadInsn.`var`) {
SmartList() SmartList()
@@ -249,7 +276,8 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
injectAssumptionsForNullCheck(varIndex, insn as JumpInsnNode) injectAssumptionsForNullCheck(varIndex, insn as JumpInsnNode)
Opcodes.INVOKESTATIC -> { Opcodes.INVOKESTATIC -> {
when { when {
insn.isCheckNotNull() || insn.isCheckParameterIsNotNull() || insn.isCheckExpressionValueIsNotNull() -> insn.isCheckNotNull() || insn.isCheckNotNullWithMessage() ||
insn.isCheckParameterIsNotNull() || insn.isCheckExpressionValueIsNotNull() ->
injectAssumptionsForNotNullAssertion(varIndex, insn) injectAssumptionsForNotNullAssertion(varIndex, insn)
insn.isPseudo(PseudoInsn.STORE_NOT_NULL) -> insn.isPseudo(PseudoInsn.STORE_NOT_NULL) ->
injectCodeForStoreNotNull(insn) injectCodeForStoreNotNull(insn)
@@ -300,13 +328,16 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
} }
private fun NullabilityAssumptions.injectAssumptionsForNotNullAssertion(varIndex: Int, insn: AbstractInsnNode) { private fun NullabilityAssumptions.injectAssumptionsForNotNullAssertion(varIndex: Int, insn: AbstractInsnNode) {
// ALOAD v // ( INVOKESTATIC checkNotNull
// DUP // | LDC <message>; INVOKESTATIC checkNotNull(Object, String)V
// INVOKESTATIC checkNotNull // )
// <...> -- v is not null here (otherwise an exception was thrown) // <...> -- v is not null here (otherwise an exception was thrown)
// ALOAD v // ALOAD v
// INVOKESTATIC checkNotNull // DUP?
// ( INVOKESTATIC checkNotNull
// | LDC <message>; INVOKESTATIC checkNotNull(Object, String)V
// )
// <...> -- v is not null here (otherwise an exception was thrown) // <...> -- v is not null here (otherwise an exception was thrown)
// ALOAD v // ALOAD v
@@ -317,7 +348,9 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
// ALOAD v // ALOAD v
// DUP // DUP
// LDC * // LDC *
// INVOKESTATIC checkExpressionValueIsNotNull/checkNotNullExpressionValue // ( INVOKESTATIC checkExpressionValueIsNotNull
// | INVOKESTATIC checkNotNullExpressionValue
// )
// <...> -- v is not null here (otherwise an exception was thrown) // <...> -- v is not null here (otherwise an exception was thrown)
methodNode.instructions.insert(insn, listOfSynthetics { methodNode.instructions.insert(insn, listOfSynthetics {
@@ -436,6 +469,13 @@ internal fun AbstractInsnNode.isCheckNotNull() =
desc == "(Ljava/lang/Object;)V" desc == "(Ljava/lang/Object;)V"
} }
internal fun AbstractInsnNode.isCheckNotNullWithMessage() =
isInsn<MethodInsnNode>(Opcodes.INVOKESTATIC) {
owner == IntrinsicMethods.INTRINSICS_CLASS_NAME &&
name == "checkNotNull" &&
desc == "(Ljava/lang/Object;Ljava/lang/String;)V"
}
fun MethodNode.usesLocalExceptParameterNullCheck(index: Int): Boolean = fun MethodNode.usesLocalExceptParameterNullCheck(index: Int): Boolean =
instructions.toArray().any { instructions.toArray().any {
it is VarInsnNode && it.opcode == Opcodes.ALOAD && it.`var` == index && !it.isParameterCheckedForNull() it is VarInsnNode && it.opcode == Opcodes.ALOAD && it.`var` == index && !it.isParameterCheckedForNull()
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
import org.jetbrains.kotlin.codegen.optimization.common.removeUnusedLocalVariables import org.jetbrains.kotlin.codegen.optimization.common.removeUnusedLocalVariables
import org.jetbrains.kotlin.codegen.optimization.nullCheck.isCheckExpressionValueIsNotNull import org.jetbrains.kotlin.codegen.optimization.nullCheck.isCheckExpressionValueIsNotNull
import org.jetbrains.kotlin.codegen.optimization.nullCheck.isCheckNotNullWithMessage
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.utils.SmartList import org.jetbrains.kotlin.utils.SmartList
@@ -288,7 +289,7 @@ class TemporaryVariablesEliminationTransformer(private val state: GenerationStat
if ((aLoad1Insn as VarInsnNode).`var` == tmp.index && if ((aLoad1Insn as VarInsnNode).`var` == tmp.index &&
(ldcInsn as LdcInsnNode).cst is String && (ldcInsn as LdcInsnNode).cst is String &&
invokeStaticInsn.isCheckExpressionValueIsNotNull() && (invokeStaticInsn.isCheckExpressionValueIsNotNull() || invokeStaticInsn.isCheckNotNullWithMessage()) &&
(aLoad2Insn as VarInsnNode).`var` == tmp.index (aLoad2Insn as VarInsnNode).`var` == tmp.index
) { ) {
// Replace instruction sequence: // Replace instruction sequence:
@@ -5506,6 +5506,12 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/elvisChain.kt"); runTest("compiler/testData/codegen/bytecodeText/temporaryVals/elvisChain.kt");
} }
@Test
@TestMetadata("noTemporaryInCheckedCast.kt")
public void testNoTemporaryInCheckedCast() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/noTemporaryInCheckedCast.kt");
}
@Test @Test
@TestMetadata("notNullReceiversInChain.kt") @TestMetadata("notNullReceiversInChain.kt")
public void testNotNullReceiversInChain() throws Exception { public void testNotNullReceiversInChain() throws Exception {
@@ -57,7 +57,9 @@ internal val typeOperatorLowering = makeIrFilePhase(
description = "Lower IrTypeOperatorCalls to (implicit) casts and instanceof checks" description = "Lower IrTypeOperatorCalls to (implicit) casts and instanceof checks"
) )
private class TypeOperatorLowering(private val context: JvmBackendContext) : FileLoweringPass, IrBuildingTransformer(context) { private class TypeOperatorLowering(private val backendContext: JvmBackendContext) :
FileLoweringPass, IrBuildingTransformer(backendContext) {
override fun lower(irFile: IrFile) = irFile.transformChildrenVoid() override fun lower(irFile: IrFile) = irFile.transformChildrenVoid()
private fun IrExpression.transformVoid() = transform(this@TypeOperatorLowering, null) private fun IrExpression.transformVoid() = transform(this@TypeOperatorLowering, null)
@@ -80,28 +82,44 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
} }
} }
private fun lowerCast(argument: IrExpression, type: IrType): IrExpression = when { private fun lowerCast(argument: IrExpression, type: IrType): IrExpression =
type.isReifiedTypeParameter -> when {
builder.irAs(argument, type) type.isReifiedTypeParameter ->
argument.type.isInlineClassType() && argument.type.isSubtypeOfClass(type.erasedUpperBound.symbol) -> builder.irAs(argument, type)
argument argument.type.isInlineClassType() && argument.type.isSubtypeOfClass(type.erasedUpperBound.symbol) ->
type.isNullable() || argument.isDefinitelyNotNull() -> argument
builder.irAs(argument, type) type.isNullable() || argument.isDefinitelyNotNull() ->
else -> { builder.irAs(argument, type)
with(builder) { else -> {
irLetS(argument, irType = context.irBuiltIns.anyNType) { valueSymbol -> with(builder) {
irIfNull( irLetS(argument, irType = context.irBuiltIns.anyNType) { tmp ->
type, val message = irString("null cannot be cast to non-null type ${type.render()}")
irGet(valueSymbol.owner), if (backendContext.state.unifiedNullChecks) {
irCall(throwTypeCastException).apply { // Avoid branching to improve code coverage (KT-27427).
putValueArgument(0, irString("null cannot be cast to non-null type ${type.render()}")) // We have to generate a null check here, because even if argument is of non-null type,
}, // it can be uninitialized value, which is 'null' for reference types in JMM.
builder.irAs(irGet(valueSymbol.owner), type.makeNullable()) // Most of such null checks will never actually throw, but we can't do anything about it.
) irBlock {
+irCall(backendContext.ir.symbols.checkNotNullWithMessage).apply {
putValueArgument(0, irGet(tmp.owner))
putValueArgument(1, message)
}
+irAs(irGet(tmp.owner), type.makeNullable())
}
} else {
irIfNull(
type,
irGet(tmp.owner),
irCall(throwTypeCastException).apply {
putValueArgument(0, message)
},
irAs(irGet(tmp.owner), type.makeNullable())
)
}
}
} }
} }
} }
}
// TODO extract null check elimination on IR somewhere? // TODO extract null check elimination on IR somewhere?
private fun IrExpression.isDefinitelyNotNull(): Boolean = private fun IrExpression.isDefinitelyNotNull(): Boolean =
@@ -112,7 +130,7 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
is IrConstructorCall -> is IrConstructorCall ->
true true
is IrCall -> is IrCall ->
this.symbol == context.irBuiltIns.checkNotNullSymbol this.symbol == backendContext.irBuiltIns.checkNotNullSymbol
else -> else ->
false false
} }
@@ -122,7 +140,7 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
return !irVariable.isVar && irVariable.initializer?.isDefinitelyNotNull() == true return !irVariable.isVar && irVariable.initializer?.isDefinitelyNotNull() == true
} }
private val jvmIndyLambdaMetafactoryIntrinsic = context.ir.symbols.indyLambdaMetafactoryIntrinsic private val jvmIndyLambdaMetafactoryIntrinsic = backendContext.ir.symbols.indyLambdaMetafactoryIntrinsic
private fun JvmIrBuilder.jvmMethodHandle(handle: Handle) = private fun JvmIrBuilder.jvmMethodHandle(handle: Handle) =
irCall(backendContext.ir.symbols.jvmMethodHandle).apply { irCall(backendContext.ir.symbols.jvmMethodHandle).apply {
@@ -278,15 +296,15 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
byDeserializedLambdaInfo[deserializedLambdaInfo] = serializableMethodRefInfo byDeserializedLambdaInfo[deserializedLambdaInfo] = serializableMethodRefInfo
} }
val deserializeLambdaFun = context.irFactory.buildFun { val deserializeLambdaFun = backendContext.irFactory.buildFun {
name = Name.identifier("\$deserializeLambda\$") name = Name.identifier("\$deserializeLambda\$")
visibility = DescriptorVisibilities.PRIVATE visibility = DescriptorVisibilities.PRIVATE
origin = JvmLoweredDeclarationOrigin.DESERIALIZE_LAMBDA_FUN origin = JvmLoweredDeclarationOrigin.DESERIALIZE_LAMBDA_FUN
} }
deserializeLambdaFun.parent = irClass deserializeLambdaFun.parent = irClass
val lambdaParameter = deserializeLambdaFun.addValueParameter("lambda", context.ir.symbols.serializedLambda.irType) val lambdaParameter = deserializeLambdaFun.addValueParameter("lambda", backendContext.ir.symbols.serializedLambda.irType)
deserializeLambdaFun.returnType = context.irBuiltIns.anyType deserializeLambdaFun.returnType = backendContext.irBuiltIns.anyType
deserializeLambdaFun.body = context.createJvmIrBuilder(deserializeLambdaFun.symbol, UNDEFINED_OFFSET, UNDEFINED_OFFSET).run { deserializeLambdaFun.body = backendContext.createJvmIrBuilder(deserializeLambdaFun.symbol, UNDEFINED_OFFSET, UNDEFINED_OFFSET).run {
irBlockBody { irBlockBody {
val tmp = irTemporary( val tmp = irTemporary(
irCall(backendContext.ir.symbols.serializedLambda.getImplMethodName).apply { irCall(backendContext.ir.symbols.serializedLambda.getImplMethodName).apply {
@@ -332,9 +350,9 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
private fun mapDeserializedLambda(info: SerializableMethodRefInfo) = private fun mapDeserializedLambda(info: SerializableMethodRefInfo) =
DeserializedLambdaInfo( DeserializedLambdaInfo(
functionalInterfaceClass = context.typeMapper.mapType(info.samType).internalName, functionalInterfaceClass = backendContext.typeMapper.mapType(info.samType).internalName,
implMethodHandle = context.methodSignatureMapper.mapToMethodHandle(info.implFunSymbol.owner), implMethodHandle = backendContext.methodSignatureMapper.mapToMethodHandle(info.implFunSymbol.owner),
functionalInterfaceMethod = context.methodSignatureMapper.mapAsmMethod(info.samMethodSymbol.owner) functionalInterfaceMethod = backendContext.methodSignatureMapper.mapAsmMethod(info.samMethodSymbol.owner)
) )
private fun JvmIrBuilder.generateSerializedLambdaEquals( private fun JvmIrBuilder.generateSerializedLambdaEquals(
@@ -376,7 +394,7 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
) )
} }
private val equalsAny = context.irBuiltIns.anyClass.getSimpleFunction("equals")!! private val equalsAny = backendContext.irBuiltIns.anyClass.getSimpleFunction("equals")!!
private fun JvmIrBuilder.irObjectEquals(receiver: IrExpression, arg: IrExpression) = private fun JvmIrBuilder.irObjectEquals(receiver: IrExpression, arg: IrExpression) =
irCall(equalsAny).apply { irCall(equalsAny).apply {
@@ -478,7 +496,7 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
) )
} }
return context.createJvmIrBuilder(implFunSymbol, startOffset, endOffset) return backendContext.createJvmIrBuilder(implFunSymbol, startOffset, endOffset)
.createLambdaMetafactoryCall( .createLambdaMetafactoryCall(
samMethod.symbol, implFunSymbol, instanceMethodRef.symbol, shouldBeSerializable, requiredBridges, dynamicCall samMethod.symbol, implFunSymbol, instanceMethodRef.symbol, shouldBeSerializable, requiredBridges, dynamicCall
) )
@@ -551,12 +569,12 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
samMethod: IrSimpleFunction, samMethod: IrSimpleFunction,
extraOverriddenMethods: List<IrSimpleFunction> extraOverriddenMethods: List<IrSimpleFunction>
): Collection<IrSimpleFunction> { ): Collection<IrSimpleFunction> {
val jvmInstanceMethod = context.methodSignatureMapper.mapAsmMethod(instanceMethod) val jvmInstanceMethod = backendContext.methodSignatureMapper.mapAsmMethod(instanceMethod)
val jvmSamMethod = context.methodSignatureMapper.mapAsmMethod(samMethod) val jvmSamMethod = backendContext.methodSignatureMapper.mapAsmMethod(samMethod)
val signatureToNonFakeOverride = LinkedHashMap<Method, IrSimpleFunction>() val signatureToNonFakeOverride = LinkedHashMap<Method, IrSimpleFunction>()
for (overridden in extraOverriddenMethods) { for (overridden in extraOverriddenMethods) {
val jvmOverriddenMethod = context.methodSignatureMapper.mapAsmMethod(overridden) val jvmOverriddenMethod = backendContext.methodSignatureMapper.mapAsmMethod(overridden)
if (jvmOverriddenMethod != jvmInstanceMethod && jvmOverriddenMethod != jvmSamMethod) { if (jvmOverriddenMethod != jvmInstanceMethod && jvmOverriddenMethod != jvmSamMethod) {
signatureToNonFakeOverride[jvmOverriddenMethod] = overridden signatureToNonFakeOverride[jvmOverriddenMethod] = overridden
} }
@@ -574,12 +592,12 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
val dynamicCallArguments = ArrayList<IrExpression>() val dynamicCallArguments = ArrayList<IrExpression>()
val irDynamicCallTarget = context.irFactory.buildFun { val irDynamicCallTarget = backendContext.irFactory.buildFun {
origin = JvmLoweredDeclarationOrigin.INVOKEDYNAMIC_CALL_TARGET origin = JvmLoweredDeclarationOrigin.INVOKEDYNAMIC_CALL_TARGET
name = samMethod.name name = samMethod.name
returnType = erasedSamType returnType = erasedSamType
}.apply { }.apply {
parent = context.ir.symbols.kotlinJvmInternalInvokeDynamicPackage parent = backendContext.ir.symbols.kotlinJvmInternalInvokeDynamicPackage
val targetFun = targetRef.symbol.owner val targetFun = targetRef.symbol.owner
val refDispatchReceiver = targetRef.dispatchReceiver val refDispatchReceiver = targetRef.dispatchReceiver
@@ -640,7 +658,7 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
) )
} }
return context.createJvmIrBuilder(irDynamicCallTarget.symbol) return backendContext.createJvmIrBuilder(irDynamicCallTarget.symbol)
.irCall(irDynamicCallTarget.symbol) .irCall(irDynamicCallTarget.symbol)
.apply { .apply {
for (i in dynamicCallArguments.indices) { for (i in dynamicCallArguments.indices) {
@@ -656,11 +674,7 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
irComposite(resultType = expression.type) { irComposite(resultType = expression.type) {
+expression.argument.transformVoid() +expression.argument.transformVoid()
// TODO: Don't generate these casts in the first place // TODO: Don't generate these casts in the first place
if (!expression.argument.type.isSubtypeOf( if (!expression.argument.type.isSubtypeOf(expression.type.makeNullable(), backendContext.typeSystem)) {
expression.type.makeNullable(),
this@TypeOperatorLowering.context.typeSystem
)
) {
+IrCompositeImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, expression.type) +IrCompositeImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, expression.type)
} }
} }
@@ -762,14 +776,11 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
declaration.fileParent.getKtFile()!!.viewProvider.contents declaration.fileParent.getKtFile()!!.viewProvider.contents
private val throwTypeCastException: IrSimpleFunctionSymbol = private val throwTypeCastException: IrSimpleFunctionSymbol =
if (context.state.unifiedNullChecks) backendContext.ir.symbols.throwTypeCastException
context.ir.symbols.throwNullPointerException
else
context.ir.symbols.throwTypeCastException
private val checkExpressionValueIsNotNull: IrSimpleFunctionSymbol = private val checkExpressionValueIsNotNull: IrSimpleFunctionSymbol =
if (context.state.unifiedNullChecks) if (backendContext.state.unifiedNullChecks)
context.ir.symbols.checkNotNullExpressionValue backendContext.ir.symbols.checkNotNullExpressionValue
else else
context.ir.symbols.checkExpressionValueIsNotNull backendContext.ir.symbols.checkExpressionValueIsNotNull
} }
@@ -145,6 +145,10 @@ class JvmSymbols(
klass.addFunction("checkNotNull", irBuiltIns.unitType, isStatic = true).apply { klass.addFunction("checkNotNull", irBuiltIns.unitType, isStatic = true).apply {
addValueParameter("object", irBuiltIns.anyNType) addValueParameter("object", irBuiltIns.anyNType)
} }
klass.addFunction("checkNotNull", irBuiltIns.unitType, isStatic = true).apply {
addValueParameter("object", irBuiltIns.anyNType)
addValueParameter("message", irBuiltIns.stringType)
}
klass.addFunction("throwNpe", irBuiltIns.unitType, isStatic = true) klass.addFunction("throwNpe", irBuiltIns.unitType, isStatic = true)
klass.declarations.add(irFactory.buildClass { klass.declarations.add(irFactory.buildClass {
@@ -162,7 +166,10 @@ class JvmSymbols(
intrinsicsClass.functions.single { it.owner.name.asString() == "checkNotNullExpressionValue" } intrinsicsClass.functions.single { it.owner.name.asString() == "checkNotNullExpressionValue" }
val checkNotNull: IrSimpleFunctionSymbol = val checkNotNull: IrSimpleFunctionSymbol =
intrinsicsClass.functions.single { it.owner.name.asString() == "checkNotNull" } intrinsicsClass.owner.functions.single { it.name.asString() == "checkNotNull" && it.valueParameters.size == 1 }.symbol
val checkNotNullWithMessage: IrSimpleFunctionSymbol =
intrinsicsClass.owner.functions.single { it.name.asString() == "checkNotNull" && it.valueParameters.size == 2 }.symbol
val throwNpe: IrSimpleFunctionSymbol = val throwNpe: IrSimpleFunctionSymbol =
intrinsicsClass.functions.single { it.owner.name.asString() == "throwNpe" } intrinsicsClass.functions.single { it.owner.name.asString() == "throwNpe" }
@@ -10,8 +10,12 @@ import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.types.typeWith
import org.jetbrains.kotlin.ir.util.isImmutable
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.primaryConstructor
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.utils.addToStdlib.assertedCast import org.jetbrains.kotlin.utils.addToStdlib.assertedCast
val IrBuilderWithScope.parent get() = scope.getLocalDeclarationParent() val IrBuilderWithScope.parent get() = scope.getLocalDeclarationParent()
@@ -23,20 +27,25 @@ inline fun IrBuilderWithScope.irLetS(
irType: IrType? = null, irType: IrType? = null,
body: (IrValueSymbol) -> IrExpression body: (IrValueSymbol) -> IrExpression
): IrExpression { ): IrExpression {
val (valueSymbol, irTemporary) = if (value is IrGetValue && value.symbol.owner.isImmutable) { val irTemporary: IrVariable?
value.symbol to null val valueSymbol: IrValueSymbol
if (value is IrGetValue && value.symbol.owner.isImmutable) {
irTemporary = null
valueSymbol = value.symbol
} else { } else {
scope.createTemporaryVariable(value, nameHint, irType = irType).let { it.symbol to it } irTemporary = scope.createTemporaryVariable(value, nameHint, irType = irType)
valueSymbol = irTemporary.symbol
} }
val irResult = body(valueSymbol) val irResult = body(valueSymbol)
return if (irTemporary == null) { if (irTemporary == null) return irResult
irResult val irBlock = IrBlockImpl(startOffset, endOffset, irResult.type, origin)
irBlock.statements.add(irTemporary)
if (irResult is IrStatementContainer) {
irBlock.statements.addAll(irResult.statements)
} else { } else {
val irBlock = IrBlockImpl(startOffset, endOffset, irResult.type, origin)
irBlock.statements.add(irTemporary)
irBlock.statements.add(irResult) irBlock.statements.add(irResult)
irBlock
} }
return irBlock
} }
fun <T : IrElement> IrStatementsBuilder<T>.irTemporary( fun <T : IrElement> IrStatementsBuilder<T>.irTemporary(
@@ -31,7 +31,7 @@ fun testDoubleArray(n: Int) =
DoubleArray(n) { it.toDouble() } DoubleArray(n) { it.toDouble() }
fun testObjectArray(n: Int) = fun testObjectArray(n: Int) =
Array(n) { it as Any } Array(n) { it.toString() }
// 0 IF_ICMPGT // 0 IF_ICMPGT
// 0 IF_CMPEQ // 0 IF_CMPEQ
@@ -26,4 +26,8 @@ fun test3() {
fun getB(): B = B() fun getB(): B = B()
// JVM_TEMPLATES
// 1 IFNONNULL // 1 IFNONNULL
// JVM_IR_TEMPLATES
// 0 IFNONNULL
// 1 INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNull \(Ljava/lang/Object;Ljava/lang/String;\)V
@@ -0,0 +1,6 @@
fun foo(): Any? = "abc"
fun test() = foo() as String
// 0 ASTORE
// 0 ALOAD
@@ -5356,6 +5356,12 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/elvisChain.kt"); runTest("compiler/testData/codegen/bytecodeText/temporaryVals/elvisChain.kt");
} }
@Test
@TestMetadata("noTemporaryInCheckedCast.kt")
public void testNoTemporaryInCheckedCast() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/noTemporaryInCheckedCast.kt");
}
@Test @Test
@TestMetadata("notNullReceiversInChain.kt") @TestMetadata("notNullReceiversInChain.kt")
public void testNotNullReceiversInChain() throws Exception { public void testNotNullReceiversInChain() throws Exception {
@@ -5506,6 +5506,12 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/elvisChain.kt"); runTest("compiler/testData/codegen/bytecodeText/temporaryVals/elvisChain.kt");
} }
@Test
@TestMetadata("noTemporaryInCheckedCast.kt")
public void testNoTemporaryInCheckedCast() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/noTemporaryInCheckedCast.kt");
}
@Test @Test
@TestMetadata("notNullReceiversInChain.kt") @TestMetadata("notNullReceiversInChain.kt")
public void testNotNullReceiversInChain() throws Exception { public void testNotNullReceiversInChain() throws Exception {