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
|
internal val AbstractInsnNode?.insnOpcodeText: String
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
null -> "null"
|
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.boxing.StackPeepholeOptimizationsTransformer
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.prepareForEmitting
|
import org.jetbrains.kotlin.codegen.optimization.common.prepareForEmitting
|
||||||
import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckMethodTransformer
|
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.optimization.transformer.CompositeMethodTransformer
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||||
import org.jetbrains.org.objectweb.asm.MethodVisitor
|
import org.jetbrains.org.objectweb.asm.MethodVisitor
|
||||||
@@ -47,6 +48,7 @@ class OptimizationMethodVisitor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val optimizationTransformer = CompositeMethodTransformer(
|
val optimizationTransformer = CompositeMethodTransformer(
|
||||||
|
CodeCompactionTransformer(),
|
||||||
CapturedVarsOptimizationMethodTransformer(),
|
CapturedVarsOptimizationMethodTransformer(),
|
||||||
RedundantNullCheckMethodTransformer(generationState),
|
RedundantNullCheckMethodTransformer(generationState),
|
||||||
RedundantCheckCastEliminationMethodTransformer(),
|
RedundantCheckCastEliminationMethodTransformer(),
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ import org.jetbrains.org.objectweb.asm.tree.TypeInsnNode
|
|||||||
class RedundantCheckCastEliminationMethodTransformer : MethodTransformer() {
|
class RedundantCheckCastEliminationMethodTransformer : MethodTransformer() {
|
||||||
override fun transform(internalClassName: String, methodNode: MethodNode) {
|
override fun transform(internalClassName: String, methodNode: MethodNode) {
|
||||||
val insns = methodNode.instructions.toArray()
|
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
|
if (insns.any { ReifiedTypeInliner.isOperationReifiedMarker(it) }) return
|
||||||
|
|
||||||
val redundantCheckCasts = ArrayList<TypeInsnNode>()
|
val redundantCheckCasts = ArrayList<TypeInsnNode>()
|
||||||
|
|||||||
+2
-2
@@ -118,9 +118,9 @@ open class FastMethodAnalyzer<V : Value>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: AnalyzerException) {
|
} 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) {
|
} 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
|
@Test
|
||||||
@TestMetadata("safeCallToPrimitiveEquality.kt")
|
@TestMetadata("safeCallToPrimitiveEquality1.kt")
|
||||||
public void testSafeCallToPrimitiveEquality() throws Exception {
|
public void testSafeCallToPrimitiveEquality1() throws Exception {
|
||||||
runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality.kt");
|
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
|
@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
|
@Nested
|
||||||
@TestMetadata("compiler/testData/codegen/bytecodeText/inline")
|
@TestMetadata("compiler/testData/codegen/bytecodeText/inline")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@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.AS
|
||||||
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.OperationKind.SAFE_AS
|
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.OperationKind.SAFE_AS
|
||||||
import org.jetbrains.kotlin.codegen.intrinsics.TypeIntrinsics
|
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.fakeAlwaysFalseIfeq
|
||||||
import org.jetbrains.kotlin.codegen.pseudoInsns.fixStackAndJump
|
import org.jetbrains.kotlin.codegen.pseudoInsns.fixStackAndJump
|
||||||
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter
|
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter
|
||||||
@@ -629,6 +630,9 @@ class ExpressionCodegen(
|
|||||||
initializer.markLineNumber(startOffset = true)
|
initializer.markLineNumber(startOffset = true)
|
||||||
value.materializeAt(varType, declaration.type)
|
value.materializeAt(varType, declaration.type)
|
||||||
declaration.markLineNumber(startOffset = true)
|
declaration.markLineNumber(startOffset = true)
|
||||||
|
if (declaration.isTemporaryVal()) {
|
||||||
|
mv.addTemporaryValInitMarker()
|
||||||
|
}
|
||||||
mv.store(index, varType)
|
mv.store(index, varType)
|
||||||
} else if (declaration.isVisibleInLVT) {
|
} else if (declaration.isVisibleInLVT) {
|
||||||
pushDefaultValueOnStack(varType, mv)
|
pushDefaultValueOnStack(varType, mv)
|
||||||
@@ -646,6 +650,11 @@ class ExpressionCodegen(
|
|||||||
return MaterialValue(this, type, expression.symbol.owner.realType)
|
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 =
|
internal fun genOrGetLocal(expression: IrExpression, type: Type, parameterType: IrType, data: BlockInfo): StackValue =
|
||||||
if (expression is IrGetValue)
|
if (expression is IrGetValue)
|
||||||
StackValue.local(
|
StackValue.local(
|
||||||
@@ -766,7 +775,7 @@ class ExpressionCodegen(
|
|||||||
val index = frameMap.getIndex(irSymbol)
|
val index = frameMap.getIndex(irSymbol)
|
||||||
if (index >= 0)
|
if (index >= 0)
|
||||||
return index
|
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 {
|
private fun handlePlusMinus(expression: IrSetValue, value: IrExpression?, isMinus: Boolean): Boolean {
|
||||||
|
|||||||
@@ -57,9 +57,8 @@ class IrCallImpl(
|
|||||||
valueArgumentsCount: Int = symbol.descriptor.valueParameters.size,
|
valueArgumentsCount: Int = symbol.descriptor.valueParameters.size,
|
||||||
origin: IrStatementOrigin? = null,
|
origin: IrStatementOrigin? = null,
|
||||||
superQualifierSymbol: IrClassSymbol? = null,
|
superQualifierSymbol: IrClassSymbol? = null,
|
||||||
) = IrCallImpl(
|
) =
|
||||||
startOffset, endOffset, type, symbol, typeArgumentsCount, valueArgumentsCount, origin, superQualifierSymbol
|
IrCallImpl(startOffset, endOffset, type, symbol, typeArgumentsCount, valueArgumentsCount, origin, superQualifierSymbol)
|
||||||
)
|
|
||||||
|
|
||||||
fun fromSymbolOwner(
|
fun fromSymbolOwner(
|
||||||
startOffset: Int,
|
startOffset: Int,
|
||||||
@@ -70,8 +69,24 @@ class IrCallImpl(
|
|||||||
valueArgumentsCount: Int = symbol.owner.valueParameters.size,
|
valueArgumentsCount: Int = symbol.owner.valueParameters.size,
|
||||||
origin: IrStatementOrigin? = null,
|
origin: IrStatementOrigin? = null,
|
||||||
superQualifierSymbol: IrClassSymbol? = 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 Long.id() = this
|
||||||
|
|
||||||
fun Short.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 doSimple1(s: String?) = s?.length == 3
|
||||||
|
|
||||||
fun doJava1(s: String?, j: J) = s?.length == j.foo()
|
|
||||||
|
|
||||||
fun doLongReceiver1(x: Long?) = x?.id() == 3L
|
fun doLongReceiver1(x: Long?) = x?.id() == 3L
|
||||||
|
|
||||||
fun doShortReceiver1(x: Short?, y: Short) = x?.id() == y
|
fun doShortReceiver1(x: Short?, y: Short) = x?.id() == y
|
||||||
|
|
||||||
fun doChain1(s: String?) = s?.drop2()?.length == 1
|
|
||||||
|
|
||||||
fun doIf1(s: String?) =
|
fun doIf1(s: String?) =
|
||||||
if (s?.length == 1) "A" else "B"
|
if (s?.length == 1) "A" else "B"
|
||||||
|
|
||||||
fun doSimple2(s: String?) = 3 == s?.length
|
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 doLongReceiver2(x: Long?) = 3L == x?.id()
|
||||||
|
|
||||||
fun doShortReceiver2(x: Short?, y: Short) = y == x?.id()
|
fun doShortReceiver2(x: Short?, y: Short) = y == x?.id()
|
||||||
|
|
||||||
fun doChain2(s: String?) = 1 == s?.drop2()?.length
|
|
||||||
|
|
||||||
fun doIf2(s: String?) =
|
fun doIf2(s: String?) =
|
||||||
if (1 == s?.length) "A" else "B"
|
if (1 == s?.length) "A" else "B"
|
||||||
|
|
||||||
// `doJava1`/`doJava2` box `s?.length` instead of unboxing `j.foo()`:
|
// 0 valueOf
|
||||||
// 2 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
|
// 0 ACONST_NULL
|
||||||
|
|
||||||
// JVM_IR_TEMPLATES
|
// JVM_IR_TEMPLATES
|
||||||
// 1 POP
|
// 2 POP
|
||||||
// 0 ACONST_NULL
|
// 0 ACONST_NULL
|
||||||
+1
-1
@@ -24,4 +24,4 @@ fun test(ss: List<String?>) {
|
|||||||
// 2 POP
|
// 2 POP
|
||||||
|
|
||||||
// JVM_IR_TEMPLATES
|
// JVM_IR_TEMPLATES
|
||||||
// 1 POP
|
// 2 POP
|
||||||
|
|||||||
+2
-2
@@ -14,12 +14,12 @@ fun main(args: Array<String>) {
|
|||||||
@BuilderInference
|
@BuilderInference
|
||||||
suspend fun SequenceScope<Int>.awaitSeq(): Int = 42
|
suspend fun SequenceScope<Int>.awaitSeq(): Int = 42
|
||||||
|
|
||||||
// 1 LINENUMBER 9 L18
|
|
||||||
|
|
||||||
// JVM_IR_TEMPLATES
|
// JVM_IR_TEMPLATES
|
||||||
|
// 1 LINENUMBER 8 L13
|
||||||
// 1 LOCALVARIABLE a I L[0-9]+ L4
|
// 1 LOCALVARIABLE a I L[0-9]+ L4
|
||||||
|
|
||||||
// JVM_TEMPLATES
|
// JVM_TEMPLATES
|
||||||
|
// 1 LINENUMBER 9 L18
|
||||||
// 1 LOCALVARIABLE a I L[0-9]+ L18
|
// 1 LOCALVARIABLE a I L[0-9]+ L18
|
||||||
|
|
||||||
// IGNORE_BACKEND_FIR: JVM_IR
|
// IGNORE_BACKEND_FIR: JVM_IR
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ fun f() {
|
|||||||
// 1 LOCALVARIABLE c C L3 L\d+ 0
|
// 1 LOCALVARIABLE c C L3 L\d+ 0
|
||||||
|
|
||||||
// JVM_IR_TEMPLATES
|
// 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 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
|
@Test
|
||||||
@TestMetadata("safeCallToPrimitiveEquality.kt")
|
@TestMetadata("safeCallToPrimitiveEquality1.kt")
|
||||||
public void testSafeCallToPrimitiveEquality() throws Exception {
|
public void testSafeCallToPrimitiveEquality1() throws Exception {
|
||||||
runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality.kt");
|
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
|
@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
|
@Nested
|
||||||
@TestMetadata("compiler/testData/codegen/bytecodeText/inline")
|
@TestMetadata("compiler/testData/codegen/bytecodeText/inline")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
|||||||
+49
-3
@@ -725,9 +725,21 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("safeCallToPrimitiveEquality.kt")
|
@TestMetadata("safeCallToPrimitiveEquality1.kt")
|
||||||
public void testSafeCallToPrimitiveEquality() throws Exception {
|
public void testSafeCallToPrimitiveEquality1() throws Exception {
|
||||||
runTest("compiler/testData/codegen/bytecodeText/boxingOptimization/safeCallToPrimitiveEquality.kt");
|
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
|
@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
|
@Nested
|
||||||
@TestMetadata("compiler/testData/codegen/bytecodeText/inline")
|
@TestMetadata("compiler/testData/codegen/bytecodeText/inline")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
|||||||
Reference in New Issue
Block a user