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:
+57
-17
@@ -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()
|
||||||
|
|||||||
+2
-1
@@ -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:
|
||||||
|
|||||||
+6
@@ -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 {
|
||||||
|
|||||||
+60
-49
@@ -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(
|
||||||
|
|||||||
+1
-1
@@ -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
|
||||||
|
|||||||
+4
@@ -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
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
fun foo(): Any? = "abc"
|
||||||
|
|
||||||
|
fun test() = foo() as String
|
||||||
|
|
||||||
|
// 0 ASTORE
|
||||||
|
// 0 ALOAD
|
||||||
+6
@@ -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 {
|
||||||
|
|||||||
+6
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user