JVM optimize out temporary variables in bytecode
This commit is contained in:
committed by
TeamCityServer
parent
bddfd086f6
commit
041773fd25
@@ -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"
|
||||
|
||||
+2
@@ -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(),
|
||||
|
||||
+1
-1
@@ -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<TypeInsnNode>()
|
||||
|
||||
+2
-2
@@ -118,9 +118,9 @@ open class FastMethodAnalyzer<V : Value>(
|
||||
}
|
||||
|
||||
} 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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+409
@@ -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<LabelNode, MutableList<AbstractInsnNode>>()
|
||||
|
||||
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<LabelNode>()
|
||||
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<LabelNode>) {
|
||||
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<TemporaryVal>) {
|
||||
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("<fake>", 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
|
||||
}
|
||||
|
||||
}
|
||||
+184
@@ -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 = "<TEMPORARY-VAL-INIT>"
|
||||
|
||||
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<VarInsnNode>
|
||||
)
|
||||
|
||||
class TemporaryValsAnalyzer {
|
||||
|
||||
fun analyze(internalClassName: String, methodNode: MethodNode): List<TemporaryVal> {
|
||||
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<AbstractInsnNode>()
|
||||
}
|
||||
|
||||
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<StoreData>, 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<AbstractInsnNode, StoreData>
|
||||
) : BasicTypeInterpreter<StoredValue>() {
|
||||
|
||||
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<StoreData> =
|
||||
when (this) {
|
||||
is StoredValue.Store -> setOf(this.temporaryVal)
|
||||
is StoredValue.DirtyStore -> this.temporaryVals
|
||||
else -> emptySet()
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
-3
@@ -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")
|
||||
|
||||
+10
-1
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+1
-19
@@ -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
|
||||
+7
@@ -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
|
||||
+16
@@ -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
|
||||
Vendored
+1
-1
@@ -13,5 +13,5 @@ fun test(x: A?) {
|
||||
// 0 ACONST_NULL
|
||||
|
||||
// JVM_IR_TEMPLATES
|
||||
// 1 POP
|
||||
// 2 POP
|
||||
// 0 ACONST_NULL
|
||||
+1
-1
@@ -24,4 +24,4 @@ fun test(ss: List<String?>) {
|
||||
// 2 POP
|
||||
|
||||
// JVM_IR_TEMPLATES
|
||||
// 1 POP
|
||||
// 2 POP
|
||||
|
||||
+2
-2
@@ -14,12 +14,12 @@ fun main(args: Array<String>) {
|
||||
@BuilderInference
|
||||
suspend fun SequenceScope<Int>.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
|
||||
|
||||
@@ -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
|
||||
// 1 LOCALVARIABLE c C L3 L\d+ 2
|
||||
@@ -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
|
||||
@@ -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
|
||||
+14
@@ -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
|
||||
+18
@@ -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
|
||||
+49
-3
@@ -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")
|
||||
|
||||
+49
-3
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user