JVM_IR more compact safe call chains (almost as old BE)
This commit is contained in:
committed by
teamcityserver
parent
f4eaf611c8
commit
0a67ab54fe
+177
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.codegen.optimization.temporaryVals
|
package org.jetbrains.kotlin.codegen.optimization.temporaryVals
|
||||||
|
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
|
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
|
||||||
|
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.transformer.MethodTransformer
|
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||||
@@ -21,6 +22,8 @@ class TemporaryVariablesEliminationTransformer(private val state: GenerationStat
|
|||||||
override fun transform(internalClassName: String, methodNode: MethodNode) {
|
override fun transform(internalClassName: String, methodNode: MethodNode) {
|
||||||
if (!state.isIrBackend) return
|
if (!state.isIrBackend) return
|
||||||
|
|
||||||
|
simplifyTrivialInstructions(methodNode)
|
||||||
|
|
||||||
val cfg = ControlFlowGraph(methodNode)
|
val cfg = ControlFlowGraph(methodNode)
|
||||||
processLabels(cfg)
|
processLabels(cfg)
|
||||||
|
|
||||||
@@ -35,6 +38,42 @@ class TemporaryVariablesEliminationTransformer(private val state: GenerationStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun simplifyTrivialInstructions(methodNode: MethodNode) {
|
||||||
|
val insnList = methodNode.instructions
|
||||||
|
for (insn in insnList.toArray()) {
|
||||||
|
when {
|
||||||
|
insn.matchOpcodes(Opcodes.ILOAD, Opcodes.POP) ||
|
||||||
|
insn.matchOpcodes(Opcodes.FLOAD, Opcodes.POP) ||
|
||||||
|
insn.matchOpcodes(Opcodes.ALOAD, Opcodes.POP) -> {
|
||||||
|
// Remove size 1 LOAD immediately followed by a POP
|
||||||
|
val popInsn = insn.next
|
||||||
|
insnList.insert(insn, InsnNode(Opcodes.NOP))
|
||||||
|
insnList.remove(insn)
|
||||||
|
insnList.remove(popInsn)
|
||||||
|
}
|
||||||
|
insn.matchOpcodes(Opcodes.DLOAD, Opcodes.POP2) ||
|
||||||
|
insn.matchOpcodes(Opcodes.LLOAD, Opcodes.POP2) -> {
|
||||||
|
// Remove size 2 LOAD immediately followed by a POP2
|
||||||
|
val pop2Insn = insn.next
|
||||||
|
insnList.insert(insn, InsnNode(Opcodes.NOP))
|
||||||
|
insnList.remove(insn)
|
||||||
|
insnList.remove(pop2Insn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (insn in insnList.toArray()) {
|
||||||
|
// Remove NOPs immediately preceded or immediately followed by an instruction that does something
|
||||||
|
// - not a LINENUMBER, not a LABEL, and not a FRAME.
|
||||||
|
if (insn.opcode == Opcodes.NOP) {
|
||||||
|
val next = insn.next
|
||||||
|
val prev = insn.previous
|
||||||
|
if (next != null && next.isMeaningful || prev != null && prev.isMeaningful) {
|
||||||
|
insnList.remove(insn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class ControlFlowGraph(val methodNode: MethodNode) {
|
private class ControlFlowGraph(val methodNode: MethodNode) {
|
||||||
private val nonTrivialPredecessors = HashMap<LabelNode, MutableList<AbstractInsnNode>>()
|
private val nonTrivialPredecessors = HashMap<LabelNode, MutableList<AbstractInsnNode>>()
|
||||||
|
|
||||||
@@ -49,6 +88,22 @@ class TemporaryVariablesEliminationTransformer(private val state: GenerationStat
|
|||||||
fun hasNonTrivialPredecessors(label: LabelNode) =
|
fun hasNonTrivialPredecessors(label: LabelNode) =
|
||||||
nonTrivialPredecessors.containsKey(label)
|
nonTrivialPredecessors.containsKey(label)
|
||||||
|
|
||||||
|
fun getAllPredecessors(label: LabelNode): List<AbstractInsnNode> {
|
||||||
|
val result = ArrayList<AbstractInsnNode>()
|
||||||
|
|
||||||
|
val trivialPredecessor = label.previous
|
||||||
|
if (trivialPredecessor.opcode != Opcodes.GOTO &&
|
||||||
|
trivialPredecessor.opcode !in Opcodes.IRETURN..Opcodes.RETURN &&
|
||||||
|
trivialPredecessor.opcode != Opcodes.ATHROW
|
||||||
|
) {
|
||||||
|
result.add(trivialPredecessor)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.addAll(nonTrivialPredecessors[label] ?: emptyList())
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
fun hasSinglePredecessor(label: LabelNode, expectedPredecessor: AbstractInsnNode): Boolean {
|
fun hasSinglePredecessor(label: LabelNode, expectedPredecessor: AbstractInsnNode): Boolean {
|
||||||
var trivialPredecessor = label.previous
|
var trivialPredecessor = label.previous
|
||||||
if (trivialPredecessor.opcode == Opcodes.GOTO ||
|
if (trivialPredecessor.opcode == Opcodes.GOTO ||
|
||||||
@@ -334,12 +389,134 @@ class TemporaryVariablesEliminationTransformer(private val state: GenerationStat
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (insn.matchOpcodes(Opcodes.ALOAD, Opcodes.IFNULL)) {
|
||||||
|
// This looks like a start of some safe call chain:
|
||||||
|
// { ... evaluate receiver ... }
|
||||||
|
// ASTORE v1
|
||||||
|
// ALOAD v1 << insn
|
||||||
|
// IFNULL L
|
||||||
|
// [ possible first receiver - ALOAD or GETSTATIC ]
|
||||||
|
// ALOAD v1
|
||||||
|
// { ... call-1 ... }
|
||||||
|
// ASTORE v2
|
||||||
|
// ALOAD v2
|
||||||
|
// IFNULL L
|
||||||
|
// [ possible first receiver - ALOAD or GETSTATIC ]
|
||||||
|
// ALOAD v2
|
||||||
|
// { ... call-2 ... }
|
||||||
|
// ...
|
||||||
|
// ASTORE vN
|
||||||
|
// ALOAD vN
|
||||||
|
// IFNULL L
|
||||||
|
// [ possible first receiver - ALOAD or GETSTATIC ]
|
||||||
|
// ALOAD vN
|
||||||
|
// { ... call-N ... }
|
||||||
|
// L:
|
||||||
|
// { ... if null ... }
|
||||||
|
|
||||||
|
val ifNull1 = insn.next as JumpInsnNode
|
||||||
|
val ifNullLabel = ifNull1.label
|
||||||
|
val predecessors = cfg.getAllPredecessors(ifNullLabel)
|
||||||
|
if (predecessors.all { isRewritableSafeCallPart(it) }) {
|
||||||
|
predecessors.forEach { rewriteSafeCallPart(it, insnList) }
|
||||||
|
insnList.insert(ifNullLabel, InsnNode(Opcodes.POP))
|
||||||
|
maxStackIncrement = max(maxStackIncrement, 1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.methodNode.maxStack += maxStackIncrement
|
cfg.methodNode.maxStack += maxStackIncrement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isRewritableSafeCallPart(branchInsn: AbstractInsnNode): Boolean {
|
||||||
|
val start = branchInsn.previous ?: return false
|
||||||
|
// ALOAD v1 << start
|
||||||
|
// IFNULL L
|
||||||
|
// [ possible first receiver - ALOAD or GETSTATIC ]
|
||||||
|
// ALOAD v1
|
||||||
|
if (start.matchOpcodes(Opcodes.ALOAD, Opcodes.IFNULL)) {
|
||||||
|
val aLoad1 = start as VarInsnNode
|
||||||
|
val ifNull = start.next as JumpInsnNode
|
||||||
|
val ifNullNext = ifNull.next
|
||||||
|
if (ifNullNext.opcode == Opcodes.ALOAD) {
|
||||||
|
val aLoad2 = ifNullNext as VarInsnNode
|
||||||
|
if (aLoad2.`var` == aLoad1.`var`) return true
|
||||||
|
val aLoad2Next = aLoad2.next
|
||||||
|
if (aLoad2Next.opcode == Opcodes.ALOAD) {
|
||||||
|
val aLoad3 = aLoad2Next as VarInsnNode
|
||||||
|
if (aLoad3.`var` == aLoad1.`var`) return true
|
||||||
|
}
|
||||||
|
} else if (ifNullNext.matchOpcodes(Opcodes.GETSTATIC, Opcodes.ALOAD)) {
|
||||||
|
val getStaticInsn = ifNullNext as FieldInsnNode
|
||||||
|
if (Type.getType(getStaticInsn.desc).size != 1) return false
|
||||||
|
val aLoad2 = getStaticInsn.next as VarInsnNode
|
||||||
|
if (aLoad2.`var` == aLoad1.`var`) return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rewriteSafeCallPart(branchInsn: AbstractInsnNode, insnList: InsnList) {
|
||||||
|
val start = branchInsn.previous
|
||||||
|
// ALOAD v1 << start
|
||||||
|
// IFNULL L
|
||||||
|
// [ possible first receiver - ALOAD or GETSTATIC ]
|
||||||
|
// ALOAD v1
|
||||||
|
val aLoad1 = start as VarInsnNode
|
||||||
|
val ifNull = start.next as JumpInsnNode
|
||||||
|
val ifNullNext = ifNull.next
|
||||||
|
if (ifNullNext.opcode == Opcodes.ALOAD) {
|
||||||
|
val aLoad2 = ifNullNext as VarInsnNode
|
||||||
|
if (aLoad2.`var` == aLoad1.`var`) {
|
||||||
|
// Rewrite
|
||||||
|
// ALOAD v1
|
||||||
|
// IFNULL L
|
||||||
|
// ALOAD v1
|
||||||
|
// to
|
||||||
|
// ALOAD v1
|
||||||
|
// DUP
|
||||||
|
// IFNULL L
|
||||||
|
insnList.insertBefore(ifNull, InsnNode(Opcodes.DUP))
|
||||||
|
insnList.remove(aLoad2)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
val aLoad3 = aLoad2.next as VarInsnNode
|
||||||
|
// Rewrite
|
||||||
|
// ALOAD v1
|
||||||
|
// IFNULL L
|
||||||
|
// ALOAD x
|
||||||
|
// ALOAD v1
|
||||||
|
// to
|
||||||
|
// ALOAD v1
|
||||||
|
// DUP
|
||||||
|
// IFNULL L
|
||||||
|
// ALOAD x
|
||||||
|
// SWAP
|
||||||
|
insnList.insertBefore(ifNull, InsnNode(Opcodes.DUP))
|
||||||
|
insnList.remove(aLoad3)
|
||||||
|
insnList.insert(aLoad2, InsnNode(Opcodes.SWAP))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Rewrite
|
||||||
|
// ALOAD v1
|
||||||
|
// IFNULL L
|
||||||
|
// GETSTATIC s
|
||||||
|
// ALOAD v1
|
||||||
|
// to
|
||||||
|
// ALOAD v1
|
||||||
|
// DUP
|
||||||
|
// IFNULL L
|
||||||
|
// GETSTATIC s
|
||||||
|
// SWAP
|
||||||
|
val aLoad2 = ifNullNext.next
|
||||||
|
insnList.insertBefore(ifNull, InsnNode(Opcodes.DUP))
|
||||||
|
insnList.remove(aLoad2)
|
||||||
|
insnList.insert(ifNullNext, InsnNode(Opcodes.SWAP))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun AbstractInsnNode.matchOpcodes(vararg opcodes: Int): Boolean {
|
private fun AbstractInsnNode.matchOpcodes(vararg opcodes: Int): Boolean {
|
||||||
var insn = this
|
var insn = this
|
||||||
for (i in opcodes.indices) {
|
for (i in opcodes.indices) {
|
||||||
|
|||||||
+18
@@ -5448,6 +5448,12 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest {
|
|||||||
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/arrayCompoundAssignment.kt");
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/arrayCompoundAssignment.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("notNullReceiversInChain.kt")
|
||||||
|
public void testNotNullReceiversInChain() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/notNullReceiversInChain.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("safeCallChain1.kt")
|
@TestMetadata("safeCallChain1.kt")
|
||||||
public void testSafeCallChain1() throws Exception {
|
public void testSafeCallChain1() throws Exception {
|
||||||
@@ -5471,6 +5477,18 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest {
|
|||||||
public void testSafeCallChainMemberExt2() throws Exception {
|
public void testSafeCallChainMemberExt2() throws Exception {
|
||||||
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/safeCallChainMemberExt2.kt");
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/safeCallChainMemberExt2.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("safeCallElvisSafeCallElvisSomething.kt")
|
||||||
|
public void testSafeCallElvisSafeCallElvisSomething() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/safeCallElvisSafeCallElvisSomething.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("safeCallWithElvis.kt")
|
||||||
|
public void testSafeCallWithElvis() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/safeCallWithElvis.kt");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
|
|||||||
@@ -412,8 +412,8 @@ private val jvmFilePhases = listOf(
|
|||||||
|
|
||||||
jvmArgumentNullabilityAssertions,
|
jvmArgumentNullabilityAssertions,
|
||||||
toArrayPhase,
|
toArrayPhase,
|
||||||
|
jvmSafeCallFoldingPhase,
|
||||||
jvmOptimizationLoweringPhase,
|
jvmOptimizationLoweringPhase,
|
||||||
ifNullExpressionsFusionPhase,
|
|
||||||
additionalClassAnnotationPhase,
|
additionalClassAnnotationPhase,
|
||||||
typeOperatorLowering,
|
typeOperatorLowering,
|
||||||
replaceKFunctionInvokeWithFunctionInvokePhase,
|
replaceKFunctionInvokeWithFunctionInvokePhase,
|
||||||
|
|||||||
+3
-96
@@ -14,19 +14,17 @@ import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
|||||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredStatementOrigin
|
import org.jetbrains.kotlin.backend.jvm.JvmLoweredStatementOrigin
|
||||||
import org.jetbrains.kotlin.backend.jvm.ir.IrInlineScopeResolver
|
import org.jetbrains.kotlin.backend.jvm.ir.IrInlineScopeResolver
|
||||||
import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder
|
|
||||||
import org.jetbrains.kotlin.backend.jvm.ir.findInlineCallSites
|
import org.jetbrains.kotlin.backend.jvm.ir.findInlineCallSites
|
||||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
import org.jetbrains.kotlin.ir.IrStatement
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
import org.jetbrains.kotlin.ir.builders.*
|
import org.jetbrains.kotlin.ir.builders.irGetField
|
||||||
|
import org.jetbrains.kotlin.ir.builders.irSetField
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.declarations.impl.IrVariableImpl
|
import org.jetbrains.kotlin.ir.declarations.impl.IrVariableImpl
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
|
||||||
import org.jetbrains.kotlin.ir.symbols.impl.IrPublicSymbolBase
|
import org.jetbrains.kotlin.ir.symbols.impl.IrPublicSymbolBase
|
||||||
import org.jetbrains.kotlin.ir.symbols.impl.IrVariableSymbolImpl
|
import org.jetbrains.kotlin.ir.symbols.impl.IrVariableSymbolImpl
|
||||||
import org.jetbrains.kotlin.ir.types.*
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
@@ -73,42 +71,6 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SafeCallInfo(
|
|
||||||
val scopeSymbol: IrSymbol,
|
|
||||||
val tmpVal: IrVariable,
|
|
||||||
val ifNullBranch: IrBranch,
|
|
||||||
val ifNotNullBranch: IrBranch
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun parseSafeCall(expression: IrExpression): SafeCallInfo? {
|
|
||||||
val block = expression as? IrBlock ?: return null
|
|
||||||
if (block.origin != IrStatementOrigin.SAFE_CALL) return null
|
|
||||||
if (block.statements.size != 2) return null
|
|
||||||
val tmpVal = block.statements[0] as? IrVariable ?: return null
|
|
||||||
val scopeOwner = tmpVal.parent as? IrDeclaration ?: return null
|
|
||||||
val scopeSymbol = scopeOwner.symbol
|
|
||||||
val whenExpr = block.statements[1] as? IrWhen ?: return null
|
|
||||||
if (whenExpr.branches.size != 2) return null
|
|
||||||
|
|
||||||
val ifNullBranch = whenExpr.branches[0]
|
|
||||||
val ifNullBranchCondition = ifNullBranch.condition
|
|
||||||
if (ifNullBranchCondition !is IrCall) return null
|
|
||||||
if (ifNullBranchCondition.symbol != context.irBuiltIns.eqeqSymbol) return null
|
|
||||||
val arg0 = ifNullBranchCondition.getValueArgument(0)
|
|
||||||
if (arg0 !is IrGetValue || arg0.symbol != tmpVal.symbol) return null
|
|
||||||
val arg1 = ifNullBranchCondition.getValueArgument(1)
|
|
||||||
if (arg1 !is IrConst<*> || arg1.value != null) return null
|
|
||||||
val ifNullBranchResult = ifNullBranch.result
|
|
||||||
if (ifNullBranchResult !is IrConst<*> || ifNullBranchResult.value != null) return null
|
|
||||||
|
|
||||||
val ifNotNullBranch = whenExpr.branches[1]
|
|
||||||
return SafeCallInfo(scopeSymbol, tmpVal, ifNullBranch, ifNotNullBranch)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun IrType.isJvmPrimitive(): Boolean =
|
|
||||||
// TODO get rid of type mapper (take care of '@EnhancedNullability', maybe some other stuff).
|
|
||||||
AsmUtil.isPrimitive(context.typeMapper.mapType(this))
|
|
||||||
|
|
||||||
override fun lower(irFile: IrFile) {
|
override fun lower(irFile: IrFile) {
|
||||||
irFile.transformChildren(Transformer(irFile.findInlineCallSites(context)), null)
|
irFile.transformChildren(Transformer(irFile.findInlineCallSites(context)), null)
|
||||||
}
|
}
|
||||||
@@ -136,61 +98,11 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass
|
|||||||
|
|
||||||
if (left.isNullConst() && right is IrConst<*> || right.isNullConst() && left is IrConst<*>)
|
if (left.isNullConst() && right is IrConst<*> || right.isNullConst() && left is IrConst<*>)
|
||||||
return IrConstImpl.constFalse(expression.startOffset, expression.endOffset, context.irBuiltIns.booleanType)
|
return IrConstImpl.constFalse(expression.startOffset, expression.endOffset, context.irBuiltIns.booleanType)
|
||||||
|
|
||||||
if (expression.symbol == context.irBuiltIns.eqeqSymbol) {
|
|
||||||
if (right.type.isJvmPrimitive()) {
|
|
||||||
parseSafeCall(left)?.let { return rewriteSafeCallEqeqPrimitive(it, expression) }
|
|
||||||
}
|
|
||||||
if (left.type.isJvmPrimitive()) {
|
|
||||||
parseSafeCall(right)?.let { return rewritePrimitiveEqeqSafeCall(it, expression) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return expression
|
return expression
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrBuilderWithScope.ifSafe(safeCall: SafeCallInfo, expr: IrExpression): IrExpression =
|
|
||||||
irBlock(origin = IrStatementOrigin.SAFE_CALL) {
|
|
||||||
+safeCall.tmpVal
|
|
||||||
+irIfThenElse(expr.type, safeCall.ifNullBranch.condition, irFalse(), expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fuse safe call with primitive equality to avoid boxing the primitive. `a?.x == p`:
|
|
||||||
// { val tmp = a; if (tmp == null) null else tmp.x } == p`
|
|
||||||
// is transformed to:
|
|
||||||
// { val tmp = a; if (tmp == null) false else tmp.x == p }
|
|
||||||
// Note that the original IR implied that `p` is always evaluated, but the rewritten version
|
|
||||||
// only does so if `a` is not null. This is how the old backend does it, and it's consistent
|
|
||||||
// with `a?.x?.equals(p)`.
|
|
||||||
private fun rewriteSafeCallEqeqPrimitive(safeCall: SafeCallInfo, eqeqCall: IrCall): IrExpression =
|
|
||||||
context.createJvmIrBuilder(safeCall.scopeSymbol).run {
|
|
||||||
ifSafe(safeCall, eqeqCall.apply { putValueArgument(0, safeCall.ifNotNullBranch.result) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fuse safe call with primitive equality to avoid boxing the primitive. 'p == a?.x':
|
|
||||||
// p == { val tmp = a; if (tmp == null) null else tmp.x }
|
|
||||||
// is transformed to:
|
|
||||||
// { val tmp_p = p; { val tmp = a; if (tmp == null) false else p == tmp } }
|
|
||||||
// Note that `p` is evaluated even if `a` is null, which is again consistent with both the old backend
|
|
||||||
// and `p.equals(a?.x)`.
|
|
||||||
private fun rewritePrimitiveEqeqSafeCall(safeCall: SafeCallInfo, eqeqCall: IrCall): IrExpression =
|
|
||||||
context.createJvmIrBuilder(safeCall.scopeSymbol).run {
|
|
||||||
val primitive = eqeqCall.getValueArgument(0)!!
|
|
||||||
if (primitive.isTrivial()) {
|
|
||||||
ifSafe(safeCall, eqeqCall.apply { putValueArgument(1, safeCall.ifNotNullBranch.result) })
|
|
||||||
} else {
|
|
||||||
// The extra block for `p`'s variable is intentional as adding it into the inner block
|
|
||||||
// would make it no longer look like a safe call to `IfNullExpressionFusionLowering`.
|
|
||||||
irBlock {
|
|
||||||
+ifSafe(safeCall, eqeqCall.apply {
|
|
||||||
putValueArgument(0, irGet(irTemporary(primitive)))
|
|
||||||
putValueArgument(1, safeCall.ifNotNullBranch.result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizePropertyAccess(expression: IrCall, data: IrDeclaration?): IrExpression {
|
private fun optimizePropertyAccess(expression: IrCall, data: IrDeclaration?): IrExpression {
|
||||||
val accessor = expression.symbol.owner as? IrSimpleFunction ?: return expression
|
val accessor = expression.symbol.owner as? IrSimpleFunction ?: return expression
|
||||||
if (accessor.modality != Modality.FINAL || accessor.isExternal) return expression
|
if (accessor.modality != Modality.FINAL || accessor.isExternal) return expression
|
||||||
@@ -370,12 +282,6 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitContainerExpression(expression: IrContainerExpression, data: IrDeclaration?): IrExpression {
|
override fun visitContainerExpression(expression: IrContainerExpression, data: IrDeclaration?): IrExpression {
|
||||||
val safeCall = parseSafeCall(expression)
|
|
||||||
if (safeCall != null) {
|
|
||||||
// Don't optimize out temporary values for safe calls (yet), so that safe call-based equality checks can be optimized.
|
|
||||||
dontTouchTemporaryVals.add(safeCall.tmpVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expression.origin == IrStatementOrigin.WHEN) {
|
if (expression.origin == IrStatementOrigin.WHEN) {
|
||||||
// Don't optimize out 'when' subject initialized with a variable,
|
// Don't optimize out 'when' subject initialized with a variable,
|
||||||
// otherwise we might get somewhat weird debugging behavior.
|
// otherwise we might get somewhat weird debugging behavior.
|
||||||
@@ -508,3 +414,4 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+398
@@ -0,0 +1,398 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 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.phaser.makeIrFilePhase
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.JvmLoweredStatementOrigin
|
||||||
|
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||||
|
import org.jetbrains.kotlin.ir.IrBuiltIns
|
||||||
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrVariable
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.impl.*
|
||||||
|
import org.jetbrains.kotlin.ir.types.IrType
|
||||||
|
import org.jetbrains.kotlin.ir.types.isNullable
|
||||||
|
import org.jetbrains.kotlin.ir.util.dump
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||||
|
|
||||||
|
|
||||||
|
val jvmSafeCallFoldingPhase = makeIrFilePhase(
|
||||||
|
::JvmSafeCallChainFoldingLowering,
|
||||||
|
name = "JvmSafeCallChainFoldingLowering",
|
||||||
|
description = "Fold safe call chains to more compact forms"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class JvmSafeCallChainFoldingLowering(val context: JvmBackendContext) : FileLoweringPass {
|
||||||
|
// Overall idea here is to represent (possibly chained) safe calls as an if-expression in the form:
|
||||||
|
// if ( { val tmp = <safe_receiver>; tmp != null } )
|
||||||
|
// <safe_call>
|
||||||
|
// else
|
||||||
|
// null
|
||||||
|
// This allows chaining safe calls like 'a?.foo()?.bar()?.qux()':
|
||||||
|
// if ( { val tmp1 = a; tmp1 != null } &&
|
||||||
|
// { val tmp2 = tmp1.foo(); tmp2 != null } &&
|
||||||
|
// { val tmp3 = tmp2.bar(); tmp3 != null }
|
||||||
|
// )
|
||||||
|
// tmp3.qux()
|
||||||
|
// else
|
||||||
|
// null
|
||||||
|
// This also allows fusing safe calls with elvises (and some other operations).
|
||||||
|
|
||||||
|
override fun lower(irFile: IrFile) {
|
||||||
|
irFile.transformChildrenVoid(Transformer())
|
||||||
|
}
|
||||||
|
|
||||||
|
private val booleanNot = context.irBuiltIns.booleanNotSymbol
|
||||||
|
|
||||||
|
private fun IrExpression.irNot() =
|
||||||
|
IrCallImpl.fromSymbolOwner(startOffset, endOffset, booleanNot).apply {
|
||||||
|
dispatchReceiver = this@irNot
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun irAndAnd(left: IrExpression, right: IrExpression): IrExpression =
|
||||||
|
IrCallImpl.fromSymbolOwner(right.startOffset, right.endOffset, context.irBuiltIns.andandSymbol).apply {
|
||||||
|
putValueArgument(0, left)
|
||||||
|
putValueArgument(1, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrExpression.irEqEqNull(): IrExpression =
|
||||||
|
IrCallImpl.fromSymbolOwner(this.startOffset, this.endOffset, context.irBuiltIns.eqeqSymbol).apply {
|
||||||
|
putValueArgument(0, this@irEqEqNull)
|
||||||
|
putValueArgument(1, IrConstImpl.constNull(startOffset, endOffset, context.irBuiltIns.nothingNType))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrExpression.wrapWithBlock(origin: IrStatementOrigin?): IrBlock =
|
||||||
|
IrBlockImpl(this.startOffset, this.endOffset, this.type, origin, listOf(this))
|
||||||
|
|
||||||
|
private fun irTrue(startOffset: Int, endOffset: Int) =
|
||||||
|
IrConstImpl.boolean(startOffset, endOffset, context.irBuiltIns.booleanType, true)
|
||||||
|
|
||||||
|
private fun irFalse(startOffset: Int, endOffset: Int) =
|
||||||
|
IrConstImpl.boolean(startOffset, endOffset, context.irBuiltIns.booleanType, false)
|
||||||
|
|
||||||
|
private fun irValNotNull(startOffset: Int, endOffset: Int, irVariable: IrVariable): IrExpression =
|
||||||
|
if (irVariable.type.isNullable())
|
||||||
|
IrGetValueImpl(startOffset, endOffset, irVariable.symbol).irEqEqNull().irNot()
|
||||||
|
else
|
||||||
|
irTrue(startOffset, endOffset)
|
||||||
|
|
||||||
|
private fun IrType.isJvmPrimitive(): Boolean =
|
||||||
|
// TODO get rid of type mapper (take care of '@EnhancedNullability', maybe some other stuff).
|
||||||
|
AsmUtil.isPrimitive(context.typeMapper.mapType(this))
|
||||||
|
|
||||||
|
private inner class Transformer : IrElementTransformerVoid() {
|
||||||
|
override fun visitBlock(expression: IrBlock): IrExpression {
|
||||||
|
expression.transformChildrenVoid()
|
||||||
|
|
||||||
|
val safeCallInfo = expression.parseSafeCall(context.irBuiltIns)
|
||||||
|
if (safeCallInfo != null) {
|
||||||
|
return foldSafeCall(safeCallInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
val elvisInfo = expression.parseElvis(context.irBuiltIns)
|
||||||
|
if (elvisInfo != null) {
|
||||||
|
return foldElvis(elvisInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO 'as?'
|
||||||
|
|
||||||
|
return expression
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun foldSafeCall(safeCallInfo: SafeCallInfo): IrExpression {
|
||||||
|
// Rewrite a safe call in the form:
|
||||||
|
// { // SAFE_CALL
|
||||||
|
// val tmp = <safe_receiver>
|
||||||
|
// if (tmp == null)
|
||||||
|
// null
|
||||||
|
// else
|
||||||
|
// <call[tmp]>
|
||||||
|
// }
|
||||||
|
val safeCallBlock = safeCallInfo.block
|
||||||
|
val startOffset = safeCallBlock.startOffset
|
||||||
|
val endOffset = safeCallBlock.endOffset
|
||||||
|
val safeCallType = safeCallBlock.type
|
||||||
|
val safeCallTmpVal = safeCallInfo.tmpVal
|
||||||
|
|
||||||
|
val tmpValInitializer = safeCallTmpVal.initializer
|
||||||
|
if (tmpValInitializer is IrBlock && tmpValInitializer.origin == JvmLoweredStatementOrigin.FOLDED_SAFE_CALL) {
|
||||||
|
// Chained safe call.
|
||||||
|
// If <safe_receiver> is a FOLDED_SAFE_CALL form, rewrite safe call to:
|
||||||
|
// { // FOLDED_SAFE_CALL
|
||||||
|
// if ( <safe_receiver_condition> && { val tmp = <safe_receiver_result>; tmp != null } )
|
||||||
|
// <call[tmp]>
|
||||||
|
// else
|
||||||
|
// null
|
||||||
|
// }
|
||||||
|
// where
|
||||||
|
// <safe_receiver> =
|
||||||
|
// { // FOLDED_SAFE_CALL
|
||||||
|
// if ( <safe_receiver_condition> )
|
||||||
|
// <safe_receiver_result>
|
||||||
|
// else
|
||||||
|
// null
|
||||||
|
// }
|
||||||
|
val foldedBlock: IrBlock = tmpValInitializer
|
||||||
|
val foldedWhen = foldedBlock.statements[0] as IrWhen
|
||||||
|
val safeReceiverCondition = foldedWhen.branches[0].condition
|
||||||
|
val safeReceiverResult = foldedWhen.branches[0].result
|
||||||
|
safeCallTmpVal.initializer = safeReceiverResult
|
||||||
|
safeCallTmpVal.type = safeReceiverResult.type
|
||||||
|
val foldedConditionPart =
|
||||||
|
IrCompositeImpl(
|
||||||
|
startOffset, endOffset, context.irBuiltIns.booleanType, null,
|
||||||
|
listOf<IrStatement>(
|
||||||
|
safeCallTmpVal,
|
||||||
|
irValNotNull(startOffset, endOffset, safeCallTmpVal)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
foldedBlock.type = safeCallType
|
||||||
|
foldedWhen.type = safeCallType
|
||||||
|
foldedWhen.branches[0].condition = irAndAnd(safeReceiverCondition, foldedConditionPart)
|
||||||
|
foldedWhen.branches[0].result = safeCallInfo.ifNotNullBranch.result
|
||||||
|
return foldedBlock
|
||||||
|
} else {
|
||||||
|
// Simple safe call.
|
||||||
|
// If <safe_receiver> itself is not a FOLDED_SAFE_CALL form, rewrite safe call to:
|
||||||
|
// { // FOLDED_SAFE_CALL
|
||||||
|
// if ( { val tmp = <safe_receiver>; tmp != null } )
|
||||||
|
// <call[tmp]>
|
||||||
|
// else
|
||||||
|
// null
|
||||||
|
// }
|
||||||
|
|
||||||
|
val foldedCondition =
|
||||||
|
IrCompositeImpl(
|
||||||
|
startOffset, endOffset, context.irBuiltIns.booleanType, null,
|
||||||
|
listOf<IrStatement>(
|
||||||
|
safeCallTmpVal,
|
||||||
|
irValNotNull(startOffset, endOffset, safeCallTmpVal)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val safeCallResult = safeCallInfo.ifNotNullBranch.result
|
||||||
|
val nullResult = safeCallInfo.ifNullBranch.result
|
||||||
|
val foldedWhen = IrWhenImpl(
|
||||||
|
startOffset, endOffset, safeCallType, JvmLoweredStatementOrigin.FOLDED_SAFE_CALL,
|
||||||
|
listOf(
|
||||||
|
IrBranchImpl(startOffset, endOffset, foldedCondition, safeCallResult),
|
||||||
|
IrBranchImpl(startOffset, endOffset, irTrue(startOffset, endOffset), nullResult)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return foldedWhen.wrapWithBlock(JvmLoweredStatementOrigin.FOLDED_SAFE_CALL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun foldElvis(elvisInfo: ElvisInfo): IrExpression {
|
||||||
|
val elvisLhs = elvisInfo.elvisLhs
|
||||||
|
val elvisBlock = elvisInfo.block
|
||||||
|
val startOffset = elvisBlock.startOffset
|
||||||
|
val endOffset = elvisBlock.endOffset
|
||||||
|
val elvisType = elvisBlock.type
|
||||||
|
val elvisTmpVal = elvisInfo.tmpVal
|
||||||
|
|
||||||
|
when {
|
||||||
|
elvisLhs is IrBlock && elvisLhs.origin == JvmLoweredStatementOrigin.FOLDED_SAFE_CALL -> {
|
||||||
|
// Fold elvis with safe call.
|
||||||
|
// Given elvis expression:
|
||||||
|
// { // ELVIS
|
||||||
|
// val tmp = <elvis_lhs>
|
||||||
|
// if (tmp == null)
|
||||||
|
// <elvis_rhs>
|
||||||
|
// else
|
||||||
|
// null
|
||||||
|
// }
|
||||||
|
// where <elvis_lhs> is a folded safe call in the form:
|
||||||
|
// { // FOLDED_SAFE_CALL
|
||||||
|
// if ( <safe_call_condition> )
|
||||||
|
// <safe_call_result>
|
||||||
|
// else
|
||||||
|
// null
|
||||||
|
// }
|
||||||
|
// rewrite it to
|
||||||
|
// { // FOLDED_ELVIS
|
||||||
|
// if ( <safe_call_condition> && { val tmp = <safe_call_result>; tmp != null } )
|
||||||
|
// tmp
|
||||||
|
// else
|
||||||
|
// <elvis_rhs>
|
||||||
|
// }
|
||||||
|
|
||||||
|
val safeCallWhen = elvisLhs.statements[0] as IrWhen
|
||||||
|
val safeCallCondition = safeCallWhen.branches[0].condition
|
||||||
|
val safeCallResult = safeCallWhen.branches[0].result
|
||||||
|
elvisTmpVal.initializer = safeCallResult
|
||||||
|
elvisTmpVal.type = safeCallResult.type
|
||||||
|
val foldedConditionPart =
|
||||||
|
IrCompositeImpl(
|
||||||
|
startOffset, endOffset, context.irBuiltIns.booleanType, null,
|
||||||
|
listOf<IrStatement>(
|
||||||
|
elvisTmpVal,
|
||||||
|
irValNotNull(startOffset, endOffset, elvisTmpVal)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val foldedWhen = IrWhenImpl(
|
||||||
|
startOffset, endOffset, elvisType, JvmLoweredStatementOrigin.FOLDED_ELVIS,
|
||||||
|
listOf(
|
||||||
|
IrBranchImpl(
|
||||||
|
startOffset, endOffset,
|
||||||
|
irAndAnd(safeCallCondition, foldedConditionPart),
|
||||||
|
IrGetValueImpl(startOffset, endOffset, elvisTmpVal.symbol)
|
||||||
|
),
|
||||||
|
IrBranchImpl(
|
||||||
|
startOffset, endOffset,
|
||||||
|
irTrue(startOffset, endOffset),
|
||||||
|
elvisInfo.elvisRhs
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return foldedWhen.wrapWithBlock(JvmLoweredStatementOrigin.FOLDED_ELVIS)
|
||||||
|
}
|
||||||
|
elvisLhs is IrBlock && elvisLhs.origin == JvmLoweredStatementOrigin.FOLDED_ELVIS -> {
|
||||||
|
// Append branches to the inner elvis:
|
||||||
|
// val t = { // FOLDED_ELVIS
|
||||||
|
// if (...) ...
|
||||||
|
// else if ...
|
||||||
|
// else <innerElvisRhs>
|
||||||
|
// }
|
||||||
|
// if (t != null) t else <outerElvisRhs>
|
||||||
|
// =>
|
||||||
|
// { // FOLDED_ELVIS
|
||||||
|
// if (...) ...
|
||||||
|
// else if ...
|
||||||
|
// else if ( { val t = <innerElvisRhs>; t != null } )
|
||||||
|
// t
|
||||||
|
// else
|
||||||
|
// <outerElvisRhs>
|
||||||
|
// }
|
||||||
|
// TODO maybe we can do somewhat better if we analyze innerElvisRhs as well
|
||||||
|
val innerElvisWhen = elvisLhs.statements[0] as IrWhen
|
||||||
|
val innerElvisLastBranch = innerElvisWhen.branches.last()
|
||||||
|
val innerElvisRhs = innerElvisLastBranch.result
|
||||||
|
elvisTmpVal.initializer = innerElvisRhs
|
||||||
|
elvisTmpVal.type = innerElvisRhs.type
|
||||||
|
val newCondition = IrCompositeImpl(
|
||||||
|
startOffset, endOffset, context.irBuiltIns.booleanType, null,
|
||||||
|
listOf(
|
||||||
|
elvisTmpVal, irValNotNull(startOffset, endOffset, elvisTmpVal)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
innerElvisLastBranch.condition = newCondition
|
||||||
|
innerElvisLastBranch.result = IrGetValueImpl(startOffset, endOffset, elvisTmpVal.symbol)
|
||||||
|
innerElvisWhen.branches.add(
|
||||||
|
IrBranchImpl(
|
||||||
|
startOffset, endOffset,
|
||||||
|
irTrue(startOffset, endOffset),
|
||||||
|
elvisInfo.elvisRhs
|
||||||
|
)
|
||||||
|
)
|
||||||
|
innerElvisWhen.type = elvisType
|
||||||
|
return innerElvisWhen.wrapWithBlock(JvmLoweredStatementOrigin.FOLDED_ELVIS)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
return elvisInfo.block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitCall(expression: IrCall): IrExpression {
|
||||||
|
expression.transformChildrenVoid()
|
||||||
|
|
||||||
|
if (expression.symbol == context.irBuiltIns.eqeqSymbol) {
|
||||||
|
val startOffset = expression.startOffset
|
||||||
|
val endOffset = expression.endOffset
|
||||||
|
|
||||||
|
val left = expression.getValueArgument(0)
|
||||||
|
?: throw AssertionError("No value argument #0: ${expression.dump()}")
|
||||||
|
val right = expression.getValueArgument(1)
|
||||||
|
?: throw AssertionError("No value argument #1: ${expression.dump()}")
|
||||||
|
if (left is IrBlock && left.origin == JvmLoweredStatementOrigin.FOLDED_SAFE_CALL && right.type.isJvmPrimitive()) {
|
||||||
|
val safeCallWhen = left.statements[0] as IrWhen
|
||||||
|
val safeCallResult = safeCallWhen.branches[0].result
|
||||||
|
expression.putValueArgument(0, safeCallResult)
|
||||||
|
safeCallWhen.branches[0].result = expression
|
||||||
|
safeCallWhen.branches[1].result = irFalse(startOffset, endOffset)
|
||||||
|
safeCallWhen.type = expression.type
|
||||||
|
return safeCallWhen.wrapWithBlock(origin = null)
|
||||||
|
}
|
||||||
|
if (right is IrBlock && right.origin == JvmLoweredStatementOrigin.FOLDED_SAFE_CALL && left.type.isJvmPrimitive()) {
|
||||||
|
val safeCallWhen = right.statements[0] as IrWhen
|
||||||
|
val safeCallResult = safeCallWhen.branches[0].result
|
||||||
|
expression.putValueArgument(1, safeCallResult)
|
||||||
|
safeCallWhen.branches[0].result = expression
|
||||||
|
safeCallWhen.branches[1].result = irFalse(startOffset, endOffset)
|
||||||
|
safeCallWhen.type = expression.type
|
||||||
|
return safeCallWhen.wrapWithBlock(origin = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class SafeCallInfo(
|
||||||
|
val block: IrBlock,
|
||||||
|
val tmpVal: IrVariable,
|
||||||
|
val ifNullBranch: IrBranch,
|
||||||
|
val ifNotNullBranch: IrBranch
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun IrBlock.parseSafeCall(irBuiltIns: IrBuiltIns): SafeCallInfo? {
|
||||||
|
if (this.origin != IrStatementOrigin.SAFE_CALL) return null
|
||||||
|
if (this.statements.size != 2) return null
|
||||||
|
val tmpVal = this.statements[0] as? IrVariable ?: return null
|
||||||
|
val whenExpr = this.statements[1] as? IrWhen ?: return null
|
||||||
|
if (whenExpr.branches.size != 2) return null
|
||||||
|
|
||||||
|
val ifNullBranch = whenExpr.branches[0]
|
||||||
|
val ifNullBranchCondition = ifNullBranch.condition
|
||||||
|
if (ifNullBranchCondition !is IrCall) return null
|
||||||
|
if (ifNullBranchCondition.symbol != irBuiltIns.eqeqSymbol) return null
|
||||||
|
val arg0 = ifNullBranchCondition.getValueArgument(0)
|
||||||
|
if (arg0 !is IrGetValue || arg0.symbol != tmpVal.symbol) return null
|
||||||
|
val arg1 = ifNullBranchCondition.getValueArgument(1)
|
||||||
|
if (arg1 !is IrConst<*> || arg1.value != null) return null
|
||||||
|
val ifNullBranchResult = ifNullBranch.result
|
||||||
|
if (ifNullBranchResult !is IrConst<*> || ifNullBranchResult.value != null) return null
|
||||||
|
|
||||||
|
val ifNotNullBranch = whenExpr.branches[1]
|
||||||
|
return SafeCallInfo(this, tmpVal, ifNullBranch, ifNotNullBranch)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class ElvisInfo(
|
||||||
|
val block: IrBlock,
|
||||||
|
val tmpVal: IrVariable,
|
||||||
|
val elvisLhs: IrExpression,
|
||||||
|
val elvisRhs: IrExpression
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun IrBlock.parseElvis(irBuiltIns: IrBuiltIns): ElvisInfo? {
|
||||||
|
if (this.origin != IrStatementOrigin.ELVIS) return null
|
||||||
|
if (this.statements.size != 2) return null
|
||||||
|
val tmpVal = this.statements[0] as? IrVariable ?: return null
|
||||||
|
val whenExpr = this.statements[1] as? IrWhen ?: return null
|
||||||
|
if (whenExpr.branches.size != 2) return null
|
||||||
|
|
||||||
|
val elvisLhs = tmpVal.initializer ?: return null
|
||||||
|
val ifNullBranch = whenExpr.branches[0]
|
||||||
|
val ifNullBranchCondition = ifNullBranch.condition
|
||||||
|
if (ifNullBranchCondition !is IrCall) return null
|
||||||
|
if (ifNullBranchCondition.symbol != irBuiltIns.eqeqSymbol) return null
|
||||||
|
val arg0 = ifNullBranchCondition.getValueArgument(0)
|
||||||
|
if (arg0 !is IrGetValue || arg0.symbol != tmpVal.symbol) return null
|
||||||
|
val arg1 = ifNullBranchCondition.getValueArgument(1)
|
||||||
|
if (arg1 !is IrConst<*> || arg1.value != null) return null
|
||||||
|
val elvisRhs = ifNullBranch.result
|
||||||
|
|
||||||
|
return ElvisInfo(this, tmpVal, elvisLhs, elvisRhs)
|
||||||
|
}
|
||||||
@@ -12,4 +12,7 @@ interface JvmLoweredStatementOrigin {
|
|||||||
object DO_WHILE_COUNTER_LOOP: IrStatementOriginImpl("DO_WHILE_COUNTER_LOOP")
|
object DO_WHILE_COUNTER_LOOP: IrStatementOriginImpl("DO_WHILE_COUNTER_LOOP")
|
||||||
object INLINE_LAMBDA : IrStatementOriginImpl("INLINE_LAMBDA")
|
object INLINE_LAMBDA : IrStatementOriginImpl("INLINE_LAMBDA")
|
||||||
object FAKE_CONTINUATION : IrStatementOriginImpl("FAKE_CONTINUATION")
|
object FAKE_CONTINUATION : IrStatementOriginImpl("FAKE_CONTINUATION")
|
||||||
|
|
||||||
|
object FOLDED_SAFE_CALL : IrStatementOriginImpl("FOLDED_SAFE_CALL")
|
||||||
|
object FOLDED_ELVIS : IrStatementOriginImpl("FOLDED_ELVIS")
|
||||||
}
|
}
|
||||||
|
|||||||
-5
@@ -6,9 +6,4 @@ fun check(a : A?) : Int {
|
|||||||
|
|
||||||
// 0 valueOf
|
// 0 valueOf
|
||||||
// 0 Value\s\(\)
|
// 0 Value\s\(\)
|
||||||
|
|
||||||
// JVM_TEMPLATES:
|
|
||||||
// 0 ACONST_NULL
|
// 0 ACONST_NULL
|
||||||
|
|
||||||
// JVM_IR_TEMPLATES:
|
|
||||||
// 1 ACONST_NULL
|
|
||||||
|
|||||||
Vendored
-5
@@ -10,9 +10,4 @@ fun check(a : A?) : Int {
|
|||||||
|
|
||||||
// 0 valueOf
|
// 0 valueOf
|
||||||
// 0 Value\s\(\)
|
// 0 Value\s\(\)
|
||||||
|
|
||||||
// JVM_TEMPLATES:
|
|
||||||
// 0 ACONST_NULL
|
// 0 ACONST_NULL
|
||||||
|
|
||||||
// JVM_IR_TEMPLATES:
|
|
||||||
// 1 ACONST_NULL
|
|
||||||
|
|||||||
Vendored
+10
-4
@@ -9,13 +9,13 @@
|
|||||||
|
|
||||||
fun box(): String {
|
fun box(): String {
|
||||||
for (i in (4 .. 1).reversed()) {
|
for (i in (4 .. 1).reversed()) {
|
||||||
throw AssertionError("Loop should not be executed")
|
throw AssertionError("Loop over empty Int range should not be executed")
|
||||||
}
|
}
|
||||||
for (i in (4L .. 1L).reversed()) {
|
for (i in (4L .. 1L).reversed()) {
|
||||||
throw AssertionError("Loop should not be executed")
|
throw AssertionError("Loop over empty Long range should not be executed")
|
||||||
}
|
}
|
||||||
for (i in ('D' .. 'A').reversed()) {
|
for (i in ('D' .. 'A').reversed()) {
|
||||||
throw AssertionError("Loop should not be executed")
|
throw AssertionError("Loop over empty Char range should not be executed")
|
||||||
}
|
}
|
||||||
return "OK"
|
return "OK"
|
||||||
}
|
}
|
||||||
@@ -29,8 +29,14 @@ fun box(): String {
|
|||||||
// 0 getStep
|
// 0 getStep
|
||||||
|
|
||||||
// JVM_IR_TEMPLATES
|
// JVM_IR_TEMPLATES
|
||||||
|
// Int- and Char-based loops are completely elimiated
|
||||||
// 0 ILOAD
|
// 0 ILOAD
|
||||||
// 2 ISTORE
|
// 0 ISTORE
|
||||||
// 0 IADD
|
// 0 IADD
|
||||||
// 0 ISUB
|
// 0 ISUB
|
||||||
// 0 IINC
|
// 0 IINC
|
||||||
|
// 3 LLOAD
|
||||||
|
// 2 LSTORE
|
||||||
|
// 1 LADD
|
||||||
|
// 0 LSUB
|
||||||
|
// 4 LDC
|
||||||
+2
-2
@@ -33,8 +33,8 @@ fun f(a: UInt): Int {
|
|||||||
|
|
||||||
|
|
||||||
// JVM_IR_TEMPLATES
|
// JVM_IR_TEMPLATES
|
||||||
// 6 ILOAD
|
// 5 ILOAD
|
||||||
// 4 ISTORE
|
// 3 ISTORE
|
||||||
// 0 IADD
|
// 0 IADD
|
||||||
// 0 ISUB
|
// 0 ISUB
|
||||||
// 2 IINC
|
// 2 IINC
|
||||||
+1
-1
@@ -30,4 +30,4 @@ suspend fun ApplicationCall.test(authenticationService: AuthenticationService) {
|
|||||||
// 0 ILOAD 3
|
// 0 ILOAD 3
|
||||||
// 0 ILOAD 2
|
// 0 ILOAD 2
|
||||||
// 1 \$i\$f\$receiveJSON I .* 2
|
// 1 \$i\$f\$receiveJSON I .* 2
|
||||||
// 3 \$i\$f\$respond I .* 3
|
// 2 \$i\$f\$respond I .* 3
|
||||||
|
|||||||
+1
-1
@@ -10,4 +10,4 @@ inline fun lookAtMe(f: (String) -> Unit) {
|
|||||||
f(a) // Should be no unneeded nops on this line, that might be generated for zero-parameters lambda
|
f(a) // Should be no unneeded nops on this line, that might be generated for zero-parameters lambda
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2 NOP
|
// 0 NOP
|
||||||
@@ -2,8 +2,4 @@ val a : Int? = 10
|
|||||||
|
|
||||||
fun foo() = a?.toString()
|
fun foo() = a?.toString()
|
||||||
|
|
||||||
// JVM_TEMPLATES
|
|
||||||
// 1 IFNULL
|
// 1 IFNULL
|
||||||
|
|
||||||
// JVM_IR_TEMPLATES
|
|
||||||
// 1 IFNONNULL
|
|
||||||
|
|||||||
+3
-3
@@ -13,6 +13,6 @@ class A(val x: String) {
|
|||||||
// 2 ACONST_NULL
|
// 2 ACONST_NULL
|
||||||
|
|
||||||
// JVM_IR_TEMPLATES
|
// JVM_IR_TEMPLATES
|
||||||
// 0 IFNULL
|
// 4 IFNULL
|
||||||
// 4 IFNONNULL
|
// 2 IFNONNULL
|
||||||
// 0 ACONST_NULL
|
// 2 ACONST_NULL
|
||||||
|
|||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
class A(val b: B)
|
||||||
|
class B(val c: C)
|
||||||
|
class C(val s: String)
|
||||||
|
|
||||||
|
fun test(na: A?) =
|
||||||
|
na?.b?.c?.s
|
||||||
|
|
||||||
|
// 1 POP
|
||||||
|
// 1 ACONST_NULL
|
||||||
|
|
||||||
|
// JVM_IR_TEMPLATES
|
||||||
|
// 1 DUP
|
||||||
|
// 1 IFNULL
|
||||||
|
|
||||||
|
// JVM_TEMPLATES
|
||||||
|
// 3 DUP
|
||||||
|
// 3 IFNULL
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fun test(a: Any?, b: Any?, c: String) =
|
||||||
|
a?.toString() ?: b?.toString() ?: c
|
||||||
|
|
||||||
|
// 2 IFNULL
|
||||||
|
// 1 ACONST_NULL
|
||||||
|
|
||||||
|
// JVM_IR_TEMPLATES
|
||||||
|
// 1 IFNONNULL
|
||||||
|
|
||||||
|
// JVM_TEMPLATES
|
||||||
|
// 2 IFNONNULL
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
fun test(a: Any?) =
|
||||||
|
a?.toString()?.hashCode() ?: 0
|
||||||
|
|
||||||
|
// 1 ALOAD
|
||||||
|
// 0 ASTORE
|
||||||
|
// 0 ISTORE
|
||||||
|
// 0 ILOAD
|
||||||
|
// 1 POP
|
||||||
|
// 0 valueOf
|
||||||
|
|
||||||
|
// JVM_IR_TEMPLATES
|
||||||
|
// 1 DUP
|
||||||
|
// 1 IFNULL
|
||||||
|
|
||||||
|
// JVM_TEMPLATES
|
||||||
|
// 2 DUP
|
||||||
|
// 2 IFNULL
|
||||||
+18
@@ -5304,6 +5304,12 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
|||||||
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/arrayCompoundAssignment.kt");
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/arrayCompoundAssignment.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("notNullReceiversInChain.kt")
|
||||||
|
public void testNotNullReceiversInChain() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/notNullReceiversInChain.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("safeCallChain1.kt")
|
@TestMetadata("safeCallChain1.kt")
|
||||||
public void testSafeCallChain1() throws Exception {
|
public void testSafeCallChain1() throws Exception {
|
||||||
@@ -5327,6 +5333,18 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
|||||||
public void testSafeCallChainMemberExt2() throws Exception {
|
public void testSafeCallChainMemberExt2() throws Exception {
|
||||||
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/safeCallChainMemberExt2.kt");
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/safeCallChainMemberExt2.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("safeCallElvisSafeCallElvisSomething.kt")
|
||||||
|
public void testSafeCallElvisSafeCallElvisSomething() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/safeCallElvisSafeCallElvisSomething.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("safeCallWithElvis.kt")
|
||||||
|
public void testSafeCallWithElvis() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/safeCallWithElvis.kt");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
|
|||||||
+18
@@ -5448,6 +5448,12 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
|||||||
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/arrayCompoundAssignment.kt");
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/arrayCompoundAssignment.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("notNullReceiversInChain.kt")
|
||||||
|
public void testNotNullReceiversInChain() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/notNullReceiversInChain.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("safeCallChain1.kt")
|
@TestMetadata("safeCallChain1.kt")
|
||||||
public void testSafeCallChain1() throws Exception {
|
public void testSafeCallChain1() throws Exception {
|
||||||
@@ -5471,6 +5477,18 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
|||||||
public void testSafeCallChainMemberExt2() throws Exception {
|
public void testSafeCallChainMemberExt2() throws Exception {
|
||||||
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/safeCallChainMemberExt2.kt");
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/safeCallChainMemberExt2.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("safeCallElvisSafeCallElvisSomething.kt")
|
||||||
|
public void testSafeCallElvisSafeCallElvisSomething() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/safeCallElvisSafeCallElvisSomething.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("safeCallWithElvis.kt")
|
||||||
|
public void testSafeCallWithElvis() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/temporaryVals/safeCallWithElvis.kt");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
|
|||||||
Reference in New Issue
Block a user