diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt index f34c2e4d8ab..0ed48ff1221 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt @@ -350,6 +350,116 @@ fun AbstractInsnNode?.insnText(insnList: InsnList): String { } } +fun MethodNode.dumpBody(): String { + val sw = StringWriter() + val pw = PrintWriter(sw) + + fun LabelNode.labelRef() = + "L#${this@dumpBody.instructions.indexOf(this)}" + + pw.println("${this.name} ${this.desc}") + + for (tcb in this.tryCatchBlocks) { + pw.println(" TRYCATCHBLOCK start:${tcb.start.labelRef()} end:${tcb.end.labelRef()} handler:${tcb.handler.labelRef()}") + } + + for ((i, insn) in this.instructions.toArray().withIndex()) { + when (insn.type) { + AbstractInsnNode.INSN -> + pw.println("$i\t${Printer.OPCODES[insn.opcode]}") + AbstractInsnNode.INT_INSN -> + pw.println("$i\t${Printer.OPCODES[insn.opcode]} ${(insn as IntInsnNode).operand}") + AbstractInsnNode.VAR_INSN -> + pw.println("$i\t${Printer.OPCODES[insn.opcode]} ${(insn as VarInsnNode).`var`}") + AbstractInsnNode.TYPE_INSN -> + pw.println("$i\t${Printer.OPCODES[insn.opcode]} ${(insn as TypeInsnNode).desc}") + AbstractInsnNode.FIELD_INSN -> { + val fieldInsn = insn as FieldInsnNode + pw.println("$i\t${Printer.OPCODES[insn.opcode]} ${fieldInsn.owner}#${fieldInsn.name} ${fieldInsn.desc}") + } + AbstractInsnNode.METHOD_INSN -> { + val methodInsn = insn as MethodInsnNode + pw.println("$i\t${Printer.OPCODES[insn.opcode]} ${methodInsn.owner}#${methodInsn.name} ${methodInsn.desc}") + } + AbstractInsnNode.INVOKE_DYNAMIC_INSN -> { + val indyInsn = insn as InvokeDynamicInsnNode + pw.println("$i\t${Printer.OPCODES[insn.opcode]} ${indyInsn.name} ${indyInsn.desc}") + val bsmTag = when (val tag = indyInsn.bsm.tag) { + Opcodes.H_GETFIELD -> "H_GETFIELD" + Opcodes.H_GETSTATIC -> "H_GETSTATIC" + Opcodes.H_PUTFIELD -> "H_PUTFIELD" + Opcodes.H_PUTSTATIC -> "H_PUTSTATIC" + Opcodes.H_INVOKEVIRTUAL -> "H_INVOKEVIRTUAL" + Opcodes.H_INVOKESTATIC -> "H_INVOKESTATIC" + Opcodes.H_INVOKESPECIAL -> "H_INVOKESPECIAL" + Opcodes.H_NEWINVOKESPECIAL -> "H_NEWINVOKESPECIAL" + Opcodes.H_INVOKEINTERFACE -> "H_INVOKEINTERFACE" + else -> "<$tag>" + } + pw.println("\t$bsmTag ${indyInsn.bsm.owner}#${indyInsn.bsm.name} ${indyInsn.bsm.desc} [") + for (bsmArg in indyInsn.bsmArgs) { + pw.println("\t$bsmArg") + } + pw.println("\t]") + } + AbstractInsnNode.JUMP_INSN -> + pw.println("$i\t${Printer.OPCODES[insn.opcode]} ${(insn as JumpInsnNode).label.labelRef()}") + AbstractInsnNode.LABEL -> + pw.println("$i\tL#$i") + AbstractInsnNode.LDC_INSN -> + pw.println("$i\tLDC ${(insn as LdcInsnNode).cst}") + AbstractInsnNode.IINC_INSN -> { + val iincInsn = insn as IincInsnNode + pw.println("$i\tIINC ${iincInsn.`var`} incr:${iincInsn.incr}") + } + AbstractInsnNode.TABLESWITCH_INSN -> { + val switchInsn = insn as TableSwitchInsnNode + pw.println("$i\tTABLESWITCH min:${switchInsn.min} max:${switchInsn.max}{") + for (k in switchInsn.labels.indices) { + pw.println("\t${k + switchInsn.min}: ${switchInsn.labels[k].labelRef()}") + } + pw.println("\tdefault: ${switchInsn.dflt.labelRef()}") + pw.println("\t}") + } + AbstractInsnNode.LOOKUPSWITCH_INSN -> { + val switchInsn = insn as LookupSwitchInsnNode + pw.println("$i\tLOOKUPSWITCH {") + for (k in switchInsn.labels.indices) { + val key = switchInsn.keys[k] + val label = switchInsn.labels[k] + pw.println("\t$key: ${label.labelRef()}") + } + pw.println("\t}") + } + AbstractInsnNode.MULTIANEWARRAY_INSN -> { + val manInsn = insn as MultiANewArrayInsnNode + pw.println("$i\t${Printer.OPCODES[insn.opcode]} ${manInsn.desc} dims:${manInsn.dims} ") + } + AbstractInsnNode.FRAME -> { + // TODO dump frame if needed + pw.println("$i\tFRAME {...}") + } + AbstractInsnNode.LINE -> { + val lineInsn = insn as LineNumberNode + pw.println("$i\tLINENUMBER ${lineInsn.line} ${lineInsn.start.labelRef()}") + } + else -> { + pw.println("$i\t??? $insn") + } + } + } + + for (lv in this.localVariables) { + pw.println(" LOCALVARIABLE ${lv.index} ${lv.name} ${lv.desc} ${lv.start.labelRef()} ${lv.end.labelRef()}") + if (lv.signature != null) { + pw.println(" // signature: ${lv.signature}") + } + } + + pw.flush() + return sw.toString() +} + internal val AbstractInsnNode?.insnOpcodeText: String get() = when (this) { null -> "null" diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt index 8da95c4a1ed..9a42d531c87 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantBoxingMethodTra import org.jetbrains.kotlin.codegen.optimization.boxing.StackPeepholeOptimizationsTransformer import org.jetbrains.kotlin.codegen.optimization.common.prepareForEmitting import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckMethodTransformer +import org.jetbrains.kotlin.codegen.optimization.temporaryVals.CodeCompactionTransformer import org.jetbrains.kotlin.codegen.optimization.transformer.CompositeMethodTransformer import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.org.objectweb.asm.MethodVisitor @@ -47,6 +48,7 @@ class OptimizationMethodVisitor( ) val optimizationTransformer = CompositeMethodTransformer( + CodeCompactionTransformer(), CapturedVarsOptimizationMethodTransformer(), RedundantNullCheckMethodTransformer(generationState), RedundantCheckCastEliminationMethodTransformer(), diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/RedundantCheckCastElimination.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/RedundantCheckCastElimination.kt index 591c89c28d2..d9c0e671c9a 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/RedundantCheckCastElimination.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/RedundantCheckCastElimination.kt @@ -28,7 +28,7 @@ import org.jetbrains.org.objectweb.asm.tree.TypeInsnNode class RedundantCheckCastEliminationMethodTransformer : MethodTransformer() { override fun transform(internalClassName: String, methodNode: MethodNode) { val insns = methodNode.instructions.toArray() - if (!insns.any { it.opcode == Opcodes.CHECKCAST}) return + if (!insns.any { it.opcode == Opcodes.CHECKCAST }) return if (insns.any { ReifiedTypeInliner.isOperationReifiedMarker(it) }) return val redundantCheckCasts = ArrayList() diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/FastMethodAnalyzer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/FastMethodAnalyzer.kt index d6d48dbfe69..5a52086427c 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/FastMethodAnalyzer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/FastMethodAnalyzer.kt @@ -118,9 +118,9 @@ open class FastMethodAnalyzer( } } catch (e: AnalyzerException) { - throw AnalyzerException(e.node, "Error at instruction #$insn ${insnNode.insnText}: ${e.message}", e) + throw AnalyzerException(e.node, "Error at instruction #$insn ${insnNode.insnText(method.instructions)}: ${e.message}", e) } catch (e: Exception) { - throw AnalyzerException(insnNode, "Error at instruction #$insn ${insnNode.insnText}: ${e.message}", e) + throw AnalyzerException(insnNode, "Error at instruction #$insn ${insnNode.insnText(method.instructions)}: ${e.message}", e) } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/CodeCompactionTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/CodeCompactionTransformer.kt new file mode 100644 index 00000000000..89f4cbd09e1 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/CodeCompactionTransformer.kt @@ -0,0 +1,409 @@ +/* + * 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.codegen.optimization.temporaryVals + +import org.jetbrains.kotlin.codegen.optimization.DeadCodeEliminationMethodTransformer +import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence +import org.jetbrains.kotlin.codegen.optimization.common.removeUnusedLocalVariables +import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer +import org.jetbrains.kotlin.utils.SmartList +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.tree.* +import kotlin.math.max + +class CodeCompactionTransformer : MethodTransformer() { + private val temporaryValsAnalyzer = TemporaryValsAnalyzer() + private val deadCodeElimination = DeadCodeEliminationMethodTransformer() + + override fun transform(internalClassName: String, methodNode: MethodNode) { + simplifyControlFlow(methodNode) + + val cfg = ControlFlowGraph(methodNode) + processLabels(cfg) + + simplifyKnownSafeCallPatterns(cfg) + + val temporaryVals = temporaryValsAnalyzer.analyze(internalClassName, methodNode) + if (temporaryVals.isNotEmpty()) { + optimizeTemporaryVals(cfg, temporaryVals) + } + + methodNode.stripTemporaryValInitMarkers() + methodNode.removeUnusedLocalVariables() + } + + + private class ControlFlowGraph(val methodNode: MethodNode) { + private val nonTrivialPredecessors = HashMap>() + + fun reset() { + nonTrivialPredecessors.clear() + } + + fun addNonTrivialPredecessor(label: LabelNode, pred: AbstractInsnNode) { + nonTrivialPredecessors.getOrPut(label) { SmartList() }.add(pred) + } + + fun hasNonTrivialPredecessors(label: LabelNode) = + nonTrivialPredecessors.containsKey(label) + + fun hasSinglePredecessor(label: LabelNode, expectedPredecessor: AbstractInsnNode): Boolean { + var trivialPredecessor = label.previous + if (trivialPredecessor.opcode == Opcodes.GOTO || + trivialPredecessor.opcode in Opcodes.IRETURN..Opcodes.RETURN || + trivialPredecessor.opcode == Opcodes.ATHROW + ) { + // Previous instruction is not a predecessor in CFG + trivialPredecessor = null + } else { + // Check trivial predecessor + if (trivialPredecessor != expectedPredecessor) return false + } + + val nonTrivialPredecessors = nonTrivialPredecessors[label] + ?: return trivialPredecessor != null + + return when { + nonTrivialPredecessors.size > 1 -> + false + nonTrivialPredecessors.size == 0 -> + trivialPredecessor == expectedPredecessor + else -> + trivialPredecessor == null && nonTrivialPredecessors[0] == expectedPredecessor + } + } + } + + + private fun processLabels(cfg: ControlFlowGraph) { + cfg.reset() + + val methodNode = cfg.methodNode + val insnList = methodNode.instructions + + val usedLabels = HashSet() + val first = insnList.first + if (first is LabelNode) { + usedLabels.add(first) + } + val last = insnList.last + if (last is LabelNode) { + usedLabels.add(last) + } + + fun addCfgEdgeToLabel(from: AbstractInsnNode, label: LabelNode) { + usedLabels.add(label) + cfg.addNonTrivialPredecessor(label, from) + } + + fun addCfgEdgesToLabels(from: AbstractInsnNode, labels: Collection) { + usedLabels.addAll(labels) + for (label in labels) { + cfg.addNonTrivialPredecessor(label, from) + } + } + + for (insn in insnList) { + when (insn.type) { + AbstractInsnNode.LINE -> { + usedLabels.add((insn as LineNumberNode).start) + } + AbstractInsnNode.JUMP_INSN -> { + addCfgEdgeToLabel(insn, (insn as JumpInsnNode).label) + } + AbstractInsnNode.LOOKUPSWITCH_INSN -> { + val switchInsn = insn as LookupSwitchInsnNode + addCfgEdgeToLabel(insn, switchInsn.dflt) + addCfgEdgesToLabels(insn, switchInsn.labels) + } + AbstractInsnNode.TABLESWITCH_INSN -> { + val switchInsn = insn as TableSwitchInsnNode + addCfgEdgeToLabel(insn, switchInsn.dflt) + addCfgEdgesToLabels(insn, switchInsn.labels) + } + } + } + for (lv in methodNode.localVariables) { + usedLabels.add(lv.start) + usedLabels.add(lv.end) + } + for (tcb in methodNode.tryCatchBlocks) { + usedLabels.add(tcb.start) + usedLabels.add(tcb.end) + addCfgEdgeToLabel(tcb.start, tcb.handler) + } + + var insn = insnList.first + while (insn != null) { + insn = if (insn is LabelNode && insn !in usedLabels) { + val next = insn.next + insnList.remove(insn) + next + } else { + insn.next + } + } + } + + private fun optimizeTemporaryVals(cfg: ControlFlowGraph, temporaryVals: List) { + val insnList = cfg.methodNode.instructions + + for (tmp in temporaryVals) { + if (tmp.loadInsns.size == 1) { + val storeInsn = tmp.storeInsn + val loadInsn = tmp.loadInsns[0] + + if (storeInsn.next == loadInsn || InsnSequence(storeInsn.next, loadInsn).none { it.isIntervening(cfg) }) { + // If there are no intervening instructions between store and load, + // drop both store and load, just keep intermediate value on stack. + // This approximately corresponds to some receiver stored in a temporary variable and immediately loaded + // (e.g., in safe call) + insnList.remove(storeInsn) + insnList.remove(loadInsn) + continue + } + + if (storeInsn.matchOpcodes(Opcodes.ASTORE, Opcodes.ALOAD, Opcodes.ALOAD)) { + val aLoad1 = storeInsn.next as VarInsnNode + val aLoad2 = aLoad1.next as VarInsnNode + if (aLoad2 == loadInsn) { + // Replace instruction sequence: + // ASTORE tmp + // ALOAD x + // ALOAD tmp + // with: + // ALOAD x + // SWAP + // This approximately corresponds to some extension receiver stored in a temporary variable and immediately loaded + // (e.g., in safe call). + insnList.remove(storeInsn) + insnList.remove(loadInsn) + insnList.insert(aLoad1, InsnNode(Opcodes.SWAP)) + continue + } + } + + if (storeInsn.matchOpcodes(Opcodes.ASTORE, Opcodes.GETSTATIC, Opcodes.ALOAD)) { + val getStaticInsn = storeInsn.next as FieldInsnNode + val aLoad2 = getStaticInsn.next as VarInsnNode + if (aLoad2 == loadInsn && Type.getType(getStaticInsn.desc).size == 1) { + // Replace instruction sequence: + // ASTORE tmp + // GETSTATIC ... // size 1 + // ALOAD tmp + // with: + // GETSTATIC ... // size 1 + // SWAP + // This approximately corresponds to some extension receiver stored in a temporary variable and immediately loaded + // (e.g., in safe call). + insnList.remove(storeInsn) + insnList.remove(loadInsn) + insnList.insert(getStaticInsn, InsnNode(Opcodes.SWAP)) + continue + } + } + } + } + } + + private fun simplifyControlFlow(methodNode: MethodNode) { + val insnList = methodNode.instructions + var needsDCE = false + + for (insn in insnList.toArray()) { + if (insn.opcode == Opcodes.NOP) { + // Remove NOPs not preceded by LABEL or LINENUMBER instructions. + val prev = insn.previous ?: continue + if (prev.type == AbstractInsnNode.LABEL || prev.type == AbstractInsnNode.LINE) continue + + insnList.remove(insn) + continue + } + + if (insn.opcode == Opcodes.GOTO) { + // If we have a GOTO instruction that leads to another GOTO, replace corresponding label. + val jumpInsn = insn as JumpInsnNode + val newLabel = jumpInsn.getFinalLabel() + if (newLabel != jumpInsn.label) { + needsDCE = true + } + jumpInsn.label = newLabel + } + } + + if (needsDCE) { + deadCodeElimination.transform("", methodNode) + } + } + + @Suppress("DuplicatedCode") + private fun simplifyKnownSafeCallPatterns(cfg: ControlFlowGraph) { + val insnList = cfg.methodNode.instructions + + var maxStackIncrement = 0 + for (insn in insnList.toArray()) { + if (insn.matchOpcodes(Opcodes.ALOAD, Opcodes.IFNONNULL)) { + // This looks like a start of some safe call: + // In a safe call, we introduce a temporary variable for a safe receiver, which is usually loaded twice: + // one time for a null check (1) and another time for an actual call (2): + // { ... evaluate receiver ... } + // ASTORE v + // ALOAD v + // IFNONNULL L + // { ... if null ... } + // L: + // [ possible first receiver - ALOAD or GETSTATIC ] + // ALOAD v + // { ... call ... } + // Try to remove a load instruction for (2), so that the safe call would look like: + // { ... evaluate receiver ... } + // ASTORE v + // ALOAD v + // DUP + // IFNONNULL L + // POP + // { ... if null ... } + // L: + // [ possible dispatch receiver - ALOAD or GETSTATIC ] + // [ SWAP if there was a dispatch receiver ] + // { ... call ... } + + val aLoad1 = insn as VarInsnNode + val ifNonNull = insn.next as JumpInsnNode + val label1 = ifNonNull.label + if (!cfg.hasSinglePredecessor(ifNonNull.label, ifNonNull)) continue + + val label1Next = label1.next ?: continue + if (label1Next.opcode == Opcodes.ALOAD) { + val aLoad2 = label1Next as VarInsnNode + if (aLoad2.`var` == aLoad1.`var`) { + // Rewrite: + // ALOAD v + // IFNONNULL L + // ... + // L: + // ALOAD v + // to: + // ALOAD v + // DUP + // IFNONNULL L + // POP + // ... + // L: + // ... + insnList.insertBefore(ifNonNull, InsnNode(Opcodes.DUP)) + insnList.insert(ifNonNull, InsnNode(Opcodes.POP)) + insnList.remove(aLoad2) + maxStackIncrement = max(maxStackIncrement, 1) + continue + } else { + val aLoad2Next = aLoad2.next + if (aLoad2Next.opcode == Opcodes.ALOAD) { + val aLoad3 = aLoad2Next as VarInsnNode + if (aLoad3.`var` == aLoad1.`var`) { + // Rewrite: + // ALOAD v + // IFNONNULL L + // ... + // L: + // ALOAD x + // ALOAD v + // to: + // ALOAD v + // DUP + // IFNONNULL L + // POP + // L: + // ALOAD x + // SWAP + insnList.insertBefore(ifNonNull, InsnNode(Opcodes.DUP)) + insnList.insert(ifNonNull, InsnNode(Opcodes.POP)) + insnList.insert(aLoad2, InsnNode(Opcodes.SWAP)) + insnList.remove(aLoad3) + maxStackIncrement = max(maxStackIncrement, 1) + continue + } + } + } + } else if (label1Next.matchOpcodes(Opcodes.GETSTATIC, Opcodes.ALOAD)) { + val getStaticInsn = label1Next as FieldInsnNode + val aLoad3 = getStaticInsn.next as VarInsnNode + if (Type.getType(getStaticInsn.desc).size == 1 && aLoad3.`var` == aLoad1.`var`) { + // Rewrite: + // ALOAD v + // IFNONNULL L + // ... + // L: + // GETSTATIC ... // size 1 + // ALOAD v + // to: + // ALOAD v + // DUP + // IFNONNULL L + // POP + // L: + // GETSTATIC ... // size 1 + // SWAP + insnList.insertBefore(ifNonNull, InsnNode(Opcodes.DUP)) + insnList.insert(ifNonNull, InsnNode(Opcodes.POP)) + insnList.insert(getStaticInsn, InsnNode(Opcodes.SWAP)) + insnList.remove(aLoad3) + maxStackIncrement = max(maxStackIncrement, 1) + continue + } + } + } + } + + cfg.methodNode.maxStack += maxStackIncrement + } + + private fun JumpInsnNode.getFinalLabel(): LabelNode { + var label = this.label + var insn = label.next + while (true) { + when { + insn.type == AbstractInsnNode.LABEL || insn.type == AbstractInsnNode.LINE -> { + insn = insn.next ?: break + } + insn.opcode == Opcodes.GOTO -> { + val newLabel = (insn as JumpInsnNode).label + if (newLabel == label) return newLabel + label = newLabel + insn = label.next ?: break + } + insn.opcode == Opcodes.NOP -> { + insn = insn.next ?: break + } + else -> break + } + } + return label + } + + private fun AbstractInsnNode.matchOpcodes(vararg opcodes: Int): Boolean { + var insn = this + for (i in opcodes.indices) { + if (insn.opcode != opcodes[i]) return false + insn = insn.next ?: return false + } + return true + } + + private fun AbstractInsnNode.isIntervening(context: ControlFlowGraph): Boolean = + when (this.type) { + AbstractInsnNode.LINE, AbstractInsnNode.FRAME -> + false + AbstractInsnNode.LABEL -> + context.hasNonTrivialPredecessors(this as LabelNode) + AbstractInsnNode.INSN -> + this.opcode != Opcodes.NOP + else -> + true + } + +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/TemporaryVals.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/TemporaryVals.kt new file mode 100644 index 00000000000..ebd6c4d20ad --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/TemporaryVals.kt @@ -0,0 +1,184 @@ +/* + * 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.codegen.optimization.temporaryVals + +import org.jetbrains.kotlin.codegen.inline.INLINE_MARKER_CLASS_NAME +import org.jetbrains.kotlin.codegen.optimization.common.FastMethodAnalyzer +import org.jetbrains.kotlin.codegen.optimization.common.isLoadOperation +import org.jetbrains.kotlin.codegen.optimization.common.isStoreOperation +import org.jetbrains.kotlin.codegen.optimization.fixStack.BasicTypeInterpreter +import org.jetbrains.kotlin.codegen.optimization.removeNodeGetNext +import org.jetbrains.kotlin.utils.SmartSet +import org.jetbrains.org.objectweb.asm.Handle +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter +import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode +import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode +import org.jetbrains.org.objectweb.asm.tree.MethodNode +import org.jetbrains.org.objectweb.asm.tree.VarInsnNode +import org.jetbrains.org.objectweb.asm.tree.analysis.Value + + +private const val TEMPORARY_VAL_INIT_MARKER = "" + +fun InstructionAdapter.addTemporaryValInitMarker() { + invokestatic(INLINE_MARKER_CLASS_NAME, TEMPORARY_VAL_INIT_MARKER, "()V", false) +} + +fun AbstractInsnNode.isTemporaryValInitMarker(): Boolean { + if (opcode != Opcodes.INVOKESTATIC) return false + val methodInsn = this as MethodInsnNode + return methodInsn.owner == INLINE_MARKER_CLASS_NAME && methodInsn.name == TEMPORARY_VAL_INIT_MARKER +} + +fun MethodNode.stripTemporaryValInitMarkers() { + var insn = this.instructions.first + while (insn != null) { + insn = if (insn.isTemporaryValInitMarker()) { + this.instructions.removeNodeGetNext(insn) + } else { + insn.next + } + } +} + +// A temporary val is a local variables that is: +// - initialized once (with some xSTORE instruction) +// - is not written by any other instruction (xSTORE or IINC) +// - not "observed" by any LVT entry +class TemporaryVal( + val index: Int, + val storeInsn: VarInsnNode, + val loadInsns: List +) + +class TemporaryValsAnalyzer { + + fun analyze(internalClassName: String, methodNode: MethodNode): List { + val temporaryValStores = methodNode.instructions.filter { it.isStoreOperation() && it.previous.isTemporaryValInitMarker() } + if (temporaryValStores.isEmpty()) return emptyList() + + val storeInsnToStoreData = temporaryValStores.associateWith { StoreData(it) } + FastMethodAnalyzer(internalClassName, methodNode, StoreTrackingInterpreter(storeInsnToStoreData)).analyze() + + return storeInsnToStoreData.values + .filterNot { it.isDirty } + .map { + val storeInsn = it.storeInsn as VarInsnNode + val loadInsns = it.loads.map { load -> load as VarInsnNode } + TemporaryVal(storeInsn.`var`, storeInsn, loadInsns) + } + .sortedBy { methodNode.instructions.indexOf(it.storeInsn) } + } + + private class StoreData(val storeInsn: AbstractInsnNode) { + var isDirty = false + + val storeOpcode = storeInsn.opcode + + val value = + StoredValue.Store( + this, + when (storeOpcode) { + Opcodes.LSTORE, Opcodes.DSTORE -> 2 + else -> 1 + } + ) + + val loads = LinkedHashSet() + } + + private sealed class StoredValue(private val _size: Int) : Value { + override fun getSize(): Int = _size + + object Small : StoredValue(1) + + object Big : StoredValue(2) + + class Store(val temporaryVal: StoreData, size: Int) : StoredValue(size) { + override fun equals(other: Any?): Boolean = + other is Store && other.temporaryVal === temporaryVal + + override fun hashCode(): Int = + temporaryVal.hashCode() + } + + class DirtyStore(val temporaryVals: Collection, size: Int) : StoredValue(size) { + override fun equals(other: Any?): Boolean = + other is DirtyStore && other.temporaryVals == temporaryVals + + override fun hashCode(): Int = + temporaryVals.hashCode() + } + } + + private class StoreTrackingInterpreter( + private val storeInsnToStoreData: Map + ) : BasicTypeInterpreter() { + + override fun uninitializedValue(): StoredValue = StoredValue.Small + override fun booleanValue(): StoredValue = StoredValue.Small + override fun charValue(): StoredValue = StoredValue.Small + override fun byteValue(): StoredValue = StoredValue.Small + override fun shortValue(): StoredValue = StoredValue.Small + override fun intValue(): StoredValue = StoredValue.Small + override fun longValue(): StoredValue = StoredValue.Big + override fun floatValue(): StoredValue = StoredValue.Small + override fun doubleValue(): StoredValue = StoredValue.Big + override fun nullValue(): StoredValue = StoredValue.Small + override fun objectValue(type: Type): StoredValue = StoredValue.Small + override fun arrayValue(type: Type): StoredValue = StoredValue.Small + override fun methodValue(type: Type): StoredValue = StoredValue.Small + override fun handleValue(handle: Handle): StoredValue = StoredValue.Small + override fun typeConstValue(typeConst: Type): StoredValue = StoredValue.Small + override fun aaLoadValue(arrayValue: StoredValue): StoredValue = StoredValue.Small + + override fun copyOperation(insn: AbstractInsnNode, value: StoredValue): StoredValue { + if (insn.isStoreOperation()) { + val temporaryValData = storeInsnToStoreData[insn] + if (temporaryValData != null) { + return temporaryValData.value + } + } else if (insn.isLoadOperation()) { + if (value is StoredValue.DirtyStore) { + // If we load a dirty value, invalidate all related temporary vals. + for (temporaryValData in value.temporaryVals) { + temporaryValData.isDirty = true + } + } else if (value is StoredValue.Store) { + // Keep track of a load instruction + value.temporaryVal.loads.add(insn) + } + } + return value + } + + override fun merge(a: StoredValue, b: StoredValue): StoredValue { + return when { + a === b -> + a + a is StoredValue.Store || a is StoredValue.DirtyStore || b is StoredValue.Store || b is StoredValue.DirtyStore -> { + // 'StoreValue.Store' are unique for each 'StoreData', so if we are here, we are going to merge value stored + // by a xSTORE instruction with some other value (maybe produced by another xSTORE instruction). + // Loading such value invalidates all related temporary vals. + val dirtySet = SmartSet.create(a.temporaryVals()) + dirtySet.addAll(b.temporaryVals()) + StoredValue.DirtyStore(dirtySet, a.size) + } + else -> + StoredValue.Small + } + } + + private fun StoredValue.temporaryVals(): Collection = + when (this) { + is StoredValue.Store -> setOf(this.temporaryVal) + is StoredValue.DirtyStore -> this.temporaryVals + else -> emptySet() + } + } +} \ No newline at end of file diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java index 1ca90a46f4f..e009c93760e 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java @@ -725,9 +725,21 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest { } @Test - @TestMetadata("safeCallToPrimitiveEquality.kt") - public void testSafeCallToPrimitiveEquality() throws Exception { - runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality.kt"); + @TestMetadata("safeCallToPrimitiveEquality1.kt") + public void testSafeCallToPrimitiveEquality1() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality1.kt"); + } + + @Test + @TestMetadata("safeCallToPrimitiveEquality2.kt") + public void testSafeCallToPrimitiveEquality2() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality2.kt"); + } + + @Test + @TestMetadata("safeCallToPrimitiveEquality3.kt") + public void testSafeCallToPrimitiveEquality3() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality3.kt"); } @Test @@ -3231,6 +3243,40 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest { } } + @Nested + @TestMetadata("compiler/testData/codegen/bytecodeText/ifNullChain") + @TestDataPath("$PROJECT_ROOT") + public class IfNullChain { + @Test + public void testAllFilesPresentInIfNullChain() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/bytecodeText/ifNullChain"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @Test + @TestMetadata("safeCallChain1.kt") + public void testSafeCallChain1() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChain1.kt"); + } + + @Test + @TestMetadata("safeCallChain2.kt") + public void testSafeCallChain2() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChain2.kt"); + } + + @Test + @TestMetadata("safeCallChainMemberExt1.kt") + public void testSafeCallChainMemberExt1() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChainMemberExt1.kt"); + } + + @Test + @TestMetadata("safeCallChainMemberExt2.kt") + public void testSafeCallChainMemberExt2() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChainMemberExt2.kt"); + } + } + @Nested @TestMetadata("compiler/testData/codegen/bytecodeText/inline") @TestDataPath("$PROJECT_ROOT") diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index 318ff97b3cb..77f87f4e82d 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.Companion.putNeedC import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.OperationKind.AS import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.OperationKind.SAFE_AS import org.jetbrains.kotlin.codegen.intrinsics.TypeIntrinsics +import org.jetbrains.kotlin.codegen.optimization.temporaryVals.addTemporaryValInitMarker import org.jetbrains.kotlin.codegen.pseudoInsns.fakeAlwaysFalseIfeq import org.jetbrains.kotlin.codegen.pseudoInsns.fixStackAndJump import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter @@ -629,6 +630,9 @@ class ExpressionCodegen( initializer.markLineNumber(startOffset = true) value.materializeAt(varType, declaration.type) declaration.markLineNumber(startOffset = true) + if (declaration.isTemporaryVal()) { + mv.addTemporaryValInitMarker() + } mv.store(index, varType) } else if (declaration.isVisibleInLVT) { pushDefaultValueOnStack(varType, mv) @@ -646,6 +650,11 @@ class ExpressionCodegen( return MaterialValue(this, type, expression.symbol.owner.realType) } + private fun IrValueDeclaration.isTemporaryVal() = + this is IrVariable && + !this.isVar && + this.origin == IrDeclarationOrigin.IR_TEMPORARY_VARIABLE + internal fun genOrGetLocal(expression: IrExpression, type: Type, parameterType: IrType, data: BlockInfo): StackValue = if (expression is IrGetValue) StackValue.local( @@ -766,7 +775,7 @@ class ExpressionCodegen( val index = frameMap.getIndex(irSymbol) if (index >= 0) return index - throw AssertionError("Non-mapped local declaration: $irSymbol\n in ${irFunction.dump()}") + throw AssertionError("Non-mapped local declaration: ${irSymbol.owner.dump()}\n in ${irFunction.dump()}") } private fun handlePlusMinus(expression: IrSetValue, value: IrExpression?, isMinus: Boolean): Boolean { diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/impl/IrCallImpl.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/impl/IrCallImpl.kt index e065bd53370..1a1d4b7f488 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/impl/IrCallImpl.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/expressions/impl/IrCallImpl.kt @@ -57,9 +57,8 @@ class IrCallImpl( valueArgumentsCount: Int = symbol.descriptor.valueParameters.size, origin: IrStatementOrigin? = null, superQualifierSymbol: IrClassSymbol? = null, - ) = IrCallImpl( - startOffset, endOffset, type, symbol, typeArgumentsCount, valueArgumentsCount, origin, superQualifierSymbol - ) + ) = + IrCallImpl(startOffset, endOffset, type, symbol, typeArgumentsCount, valueArgumentsCount, origin, superQualifierSymbol) fun fromSymbolOwner( startOffset: Int, @@ -70,8 +69,24 @@ class IrCallImpl( valueArgumentsCount: Int = symbol.owner.valueParameters.size, origin: IrStatementOrigin? = null, superQualifierSymbol: IrClassSymbol? = null, - ) = IrCallImpl( - startOffset, endOffset, type, symbol, typeArgumentsCount, valueArgumentsCount, origin, superQualifierSymbol - ) + ) = + IrCallImpl(startOffset, endOffset, type, symbol, typeArgumentsCount, valueArgumentsCount, origin, superQualifierSymbol) + + fun fromSymbolOwner( + startOffset: Int, + endOffset: Int, + symbol: IrSimpleFunctionSymbol + ) = + IrCallImpl( + startOffset, + endOffset, + symbol.owner.returnType, + symbol, + typeArgumentsCount = symbol.owner.typeParameters.size, + valueArgumentsCount = symbol.owner.valueParameters.size, + origin = null, + superQualifierSymbol = null + ) + } } diff --git a/compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality.kt b/compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality1.kt similarity index 55% rename from compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality.kt rename to compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality1.kt index d1d93fbe875..4e832b5c9b9 100644 --- a/compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality.kt +++ b/compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality1.kt @@ -1,12 +1,3 @@ -// FILE: J.java -import org.jetbrains.annotations.NotNull; - -public interface J { - @NotNull - public Integer foo(); -} - -// FILE: test.kt fun Long.id() = this fun Short.id() = this @@ -15,29 +6,20 @@ fun String.drop2() = if (length >= 2) subSequence(2, length) else null fun doSimple1(s: String?) = s?.length == 3 -fun doJava1(s: String?, j: J) = s?.length == j.foo() - fun doLongReceiver1(x: Long?) = x?.id() == 3L fun doShortReceiver1(x: Short?, y: Short) = x?.id() == y -fun doChain1(s: String?) = s?.drop2()?.length == 1 - fun doIf1(s: String?) = if (s?.length == 1) "A" else "B" fun doSimple2(s: String?) = 3 == s?.length -fun doJava2(s: String?, j: J) = j.foo() == s?.length - fun doLongReceiver2(x: Long?) = 3L == x?.id() fun doShortReceiver2(x: Short?, y: Short) = y == x?.id() -fun doChain2(s: String?) = 1 == s?.drop2()?.length - fun doIf2(s: String?) = if (1 == s?.length) "A" else "B" -// `doJava1`/`doJava2` box `s?.length` instead of unboxing `j.foo()`: -// 2 valueOf +// 0 valueOf diff --git a/compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality2.kt b/compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality2.kt new file mode 100644 index 00000000000..7e5ea9d55dd --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality2.kt @@ -0,0 +1,7 @@ +fun String.drop2() = if (length >= 2) subSequence(2, length) else null + +fun doChain1(s: String?) = s?.drop2()?.length == 1 + +fun doChain2(s: String?) = 1 == s?.drop2()?.length + +// 0 valueOf diff --git a/compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality3.kt b/compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality3.kt new file mode 100644 index 00000000000..e2355de602d --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality3.kt @@ -0,0 +1,16 @@ +// FILE: J.java +import org.jetbrains.annotations.NotNull; + +public interface J { + @NotNull + public Integer foo(); +} + +// FILE: safeCallToPrimitiveEquality3.kt + +fun doJava1(s: String?, j: J) = s?.length == j.foo() + +fun doJava2(s: String?, j: J) = j.foo() == s?.length + +// `doJava1`/`doJava2` box `s?.length` instead of unboxing `j.foo()`: +// 2 valueOf diff --git a/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/safeCallWithReturnValue.kt b/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/safeCallWithReturnValue.kt index b59d8f9c251..00019742547 100644 --- a/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/safeCallWithReturnValue.kt +++ b/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/safeCallWithReturnValue.kt @@ -13,5 +13,5 @@ fun test(x: A?) { // 0 ACONST_NULL // JVM_IR_TEMPLATES -// 1 POP +// 2 POP // 0 ACONST_NULL \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/safeLet.kt b/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/safeLet.kt index 51825c13f29..ef7af973d3f 100644 --- a/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/safeLet.kt +++ b/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/safeLet.kt @@ -24,4 +24,4 @@ fun test(ss: List) { // 2 POP // JVM_IR_TEMPLATES -// 1 POP +// 2 POP diff --git a/compiler/testData/codegen/bytecodeText/coroutines/debug/localVariableCorrectLabel.kt b/compiler/testData/codegen/bytecodeText/coroutines/debug/localVariableCorrectLabel.kt index 28c410d1313..08be3e69140 100644 --- a/compiler/testData/codegen/bytecodeText/coroutines/debug/localVariableCorrectLabel.kt +++ b/compiler/testData/codegen/bytecodeText/coroutines/debug/localVariableCorrectLabel.kt @@ -14,12 +14,12 @@ fun main(args: Array) { @BuilderInference suspend fun SequenceScope.awaitSeq(): Int = 42 -// 1 LINENUMBER 9 L18 - // JVM_IR_TEMPLATES +// 1 LINENUMBER 8 L13 // 1 LOCALVARIABLE a I L[0-9]+ L4 // JVM_TEMPLATES +// 1 LINENUMBER 9 L18 // 1 LOCALVARIABLE a I L[0-9]+ L18 // IGNORE_BACKEND_FIR: JVM_IR diff --git a/compiler/testData/codegen/bytecodeText/forLoop/loopVarInterval.kt b/compiler/testData/codegen/bytecodeText/forLoop/loopVarInterval.kt index a5ca5946e9c..eef5cc4de93 100644 --- a/compiler/testData/codegen/bytecodeText/forLoop/loopVarInterval.kt +++ b/compiler/testData/codegen/bytecodeText/forLoop/loopVarInterval.kt @@ -12,6 +12,6 @@ fun f() { // 1 LOCALVARIABLE c C L3 L\d+ 0 // JVM_IR_TEMPLATES -// 1 ISTORE 2\s+L4 +// 1 ISTORE 2\s+L3 // 1 ILOAD 2\s+INVOKEVIRTUAL java/io/PrintStream.print \(C\)V -// 1 LOCALVARIABLE c C L4 L\d+ 2 \ No newline at end of file +// 1 LOCALVARIABLE c C L3 L\d+ 2 \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChain1.kt b/compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChain1.kt new file mode 100644 index 00000000000..1e73d132ae4 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChain1.kt @@ -0,0 +1,8 @@ +class A(val b: B) +class B(val c: C) +class C(val s: String) + +fun test(an: A?) = an?.b?.c?.s + +// JVM_IR_TEMPLATES +// 0 ASTORE diff --git a/compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChain2.kt b/compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChain2.kt new file mode 100644 index 00000000000..a49bc8b8f09 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChain2.kt @@ -0,0 +1,8 @@ +class A(val bn: B?) +class B(val cn: C?) +class C(val s: String) + +fun test(an: A?) = an?.bn?.cn?.s + +// JVM_IR_TEMPLATES +// 0 ASTORE diff --git a/compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChainMemberExt1.kt b/compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChainMemberExt1.kt new file mode 100644 index 00000000000..793b4416fd8 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChainMemberExt1.kt @@ -0,0 +1,14 @@ +class A +class B +class C + +object Host { + val A.b: B get() = B() + val B.c: C get() = C() + val C.s: String get() = "s" + + fun test(an: A?) = an?.b?.c?.s +} + +// JVM_IR_TEMPLATES +// 0 ASTORE diff --git a/compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChainMemberExt2.kt b/compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChainMemberExt2.kt new file mode 100644 index 00000000000..b7ea20331f6 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChainMemberExt2.kt @@ -0,0 +1,18 @@ +import Host.b +import Host.c +import Host.s + +class A +class B +class C + +object Host { + val A.b: B get() = B() + val B.c: C get() = C() + val C.s: String get() = "s" +} + +fun test(an: A?) = an?.b?.c?.s + +// JVM_IR_TEMPLATES +// 0 ASTORE diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BytecodeTextTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BytecodeTextTestGenerated.java index 98f5ed62474..cfd8337098d 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BytecodeTextTestGenerated.java @@ -713,9 +713,21 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { } @Test - @TestMetadata("safeCallToPrimitiveEquality.kt") - public void testSafeCallToPrimitiveEquality() throws Exception { - runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality.kt"); + @TestMetadata("safeCallToPrimitiveEquality1.kt") + public void testSafeCallToPrimitiveEquality1() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality1.kt"); + } + + @Test + @TestMetadata("safeCallToPrimitiveEquality2.kt") + public void testSafeCallToPrimitiveEquality2() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality2.kt"); + } + + @Test + @TestMetadata("safeCallToPrimitiveEquality3.kt") + public void testSafeCallToPrimitiveEquality3() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality3.kt"); } @Test @@ -3087,6 +3099,40 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { } } + @Nested + @TestMetadata("compiler/testData/codegen/bytecodeText/ifNullChain") + @TestDataPath("$PROJECT_ROOT") + public class IfNullChain { + @Test + public void testAllFilesPresentInIfNullChain() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/bytecodeText/ifNullChain"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); + } + + @Test + @TestMetadata("safeCallChain1.kt") + public void testSafeCallChain1() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChain1.kt"); + } + + @Test + @TestMetadata("safeCallChain2.kt") + public void testSafeCallChain2() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChain2.kt"); + } + + @Test + @TestMetadata("safeCallChainMemberExt1.kt") + public void testSafeCallChainMemberExt1() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChainMemberExt1.kt"); + } + + @Test + @TestMetadata("safeCallChainMemberExt2.kt") + public void testSafeCallChainMemberExt2() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChainMemberExt2.kt"); + } + } + @Nested @TestMetadata("compiler/testData/codegen/bytecodeText/inline") @TestDataPath("$PROJECT_ROOT") diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBytecodeTextTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBytecodeTextTestGenerated.java index ab03464c687..ca7a644fa33 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBytecodeTextTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBytecodeTextTestGenerated.java @@ -725,9 +725,21 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest { } @Test - @TestMetadata("safeCallToPrimitiveEquality.kt") - public void testSafeCallToPrimitiveEquality() throws Exception { - runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality.kt"); + @TestMetadata("safeCallToPrimitiveEquality1.kt") + public void testSafeCallToPrimitiveEquality1() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality1.kt"); + } + + @Test + @TestMetadata("safeCallToPrimitiveEquality2.kt") + public void testSafeCallToPrimitiveEquality2() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality2.kt"); + } + + @Test + @TestMetadata("safeCallToPrimitiveEquality3.kt") + public void testSafeCallToPrimitiveEquality3() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality3.kt"); } @Test @@ -3231,6 +3243,40 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest { } } + @Nested + @TestMetadata("compiler/testData/codegen/bytecodeText/ifNullChain") + @TestDataPath("$PROJECT_ROOT") + public class IfNullChain { + @Test + public void testAllFilesPresentInIfNullChain() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/bytecodeText/ifNullChain"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @Test + @TestMetadata("safeCallChain1.kt") + public void testSafeCallChain1() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChain1.kt"); + } + + @Test + @TestMetadata("safeCallChain2.kt") + public void testSafeCallChain2() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChain2.kt"); + } + + @Test + @TestMetadata("safeCallChainMemberExt1.kt") + public void testSafeCallChainMemberExt1() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChainMemberExt1.kt"); + } + + @Test + @TestMetadata("safeCallChainMemberExt2.kt") + public void testSafeCallChainMemberExt2() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/ifNullChain/safeCallChainMemberExt2.kt"); + } + } + @Nested @TestMetadata("compiler/testData/codegen/bytecodeText/inline") @TestDataPath("$PROJECT_ROOT")