JVM infer temporary vals from bytecode
This commit is contained in:
committed by
TeamCityServer
parent
a4e299b8e1
commit
dcbc2ea2b3
+3
-3
@@ -23,7 +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.temporaryVals.TemporaryVariablesEliminationTransformer
|
||||||
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
|
||||||
@@ -48,12 +48,12 @@ class OptimizationMethodVisitor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val optimizationTransformer = CompositeMethodTransformer(
|
val optimizationTransformer = CompositeMethodTransformer(
|
||||||
CodeCompactionTransformer(),
|
|
||||||
CapturedVarsOptimizationMethodTransformer(),
|
CapturedVarsOptimizationMethodTransformer(),
|
||||||
RedundantNullCheckMethodTransformer(generationState),
|
RedundantNullCheckMethodTransformer(generationState),
|
||||||
RedundantCheckCastEliminationMethodTransformer(),
|
RedundantCheckCastEliminationMethodTransformer(),
|
||||||
ConstantConditionEliminationMethodTransformer(),
|
ConstantConditionEliminationMethodTransformer(),
|
||||||
RedundantBoxingMethodTransformer(generationState),
|
RedundantBoxingMethodTransformer(generationState),
|
||||||
|
TemporaryVariablesEliminationTransformer(),
|
||||||
StackPeepholeOptimizationsTransformer(),
|
StackPeepholeOptimizationsTransformer(),
|
||||||
PopBackwardPropagationTransformer(),
|
PopBackwardPropagationTransformer(),
|
||||||
DeadCodeEliminationMethodTransformer(),
|
DeadCodeEliminationMethodTransformer(),
|
||||||
@@ -76,7 +76,7 @@ class OptimizationMethodVisitor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val MEMORY_LIMIT_BY_METHOD_MB = 50
|
const val MEMORY_LIMIT_BY_METHOD_MB = 50
|
||||||
private const val TRY_CATCH_BLOCKS_SOFT_LIMIT = 16
|
private const val TRY_CATCH_BLOCKS_SOFT_LIMIT = 16
|
||||||
|
|
||||||
fun canBeOptimized(node: MethodNode): Boolean {
|
fun canBeOptimized(node: MethodNode): Boolean {
|
||||||
|
|||||||
+316
@@ -0,0 +1,316 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* ASM: a very small and fast Java bytecode manipulation framework
|
||||||
|
* Copyright (c) 2000-2011 INRIA, France Telecom
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of the copyright holders nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from
|
||||||
|
* this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||||
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.codegen.optimization.temporaryVals
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.codegen.inline.insnText
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
|
||||||
|
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 org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException
|
||||||
|
|
||||||
|
interface StoreLoadValue
|
||||||
|
|
||||||
|
|
||||||
|
interface StoreLoadInterpreter<V : StoreLoadValue> {
|
||||||
|
fun uninitialized(): V
|
||||||
|
fun valueParameter(type: Type): V
|
||||||
|
fun store(insn: VarInsnNode): V
|
||||||
|
fun load(insn: VarInsnNode, value: V)
|
||||||
|
fun iinc(insn: IincInsnNode, value: V): V
|
||||||
|
fun merge(a: V, b: V): V
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class StoreLoadFrame<V : StoreLoadValue>(val maxLocals: Int) {
|
||||||
|
private val locals = arrayOfNulls<StoreLoadValue>(maxLocals)
|
||||||
|
|
||||||
|
operator fun get(index: Int): V =
|
||||||
|
locals[index] as V
|
||||||
|
|
||||||
|
operator fun set(index: Int, newValue: V) {
|
||||||
|
locals[index] = newValue
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(other: StoreLoadFrame<V>): StoreLoadFrame<V> {
|
||||||
|
System.arraycopy(other.locals, 0, this.locals, 0, locals.size)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun execute(insn: AbstractInsnNode, interpreter: StoreLoadInterpreter<V>) {
|
||||||
|
when (insn.opcode) {
|
||||||
|
in Opcodes.ISTORE..Opcodes.ASTORE -> {
|
||||||
|
val varInsn = insn as VarInsnNode
|
||||||
|
locals[varInsn.`var`] = interpreter.store(varInsn)
|
||||||
|
}
|
||||||
|
in Opcodes.ILOAD..Opcodes.ALOAD -> {
|
||||||
|
val varInsn = insn as VarInsnNode
|
||||||
|
interpreter.load(varInsn, this[varInsn.`var`])
|
||||||
|
}
|
||||||
|
Opcodes.IINC -> {
|
||||||
|
val iincInsn = insn as IincInsnNode
|
||||||
|
interpreter.iinc(iincInsn, this[iincInsn.`var`])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun merge(other: StoreLoadFrame<V>, interpreter: StoreLoadInterpreter<V>): Boolean {
|
||||||
|
var changes = false
|
||||||
|
for (i in locals.indices) {
|
||||||
|
val oldValue = this[i]
|
||||||
|
val newValue = interpreter.merge(oldValue, other[i])
|
||||||
|
if (newValue != oldValue) {
|
||||||
|
changes = true
|
||||||
|
this[i] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal val isOpcodeRelevantForStoreLoadAnalysis = BooleanArray(256).also { a ->
|
||||||
|
for (i in Opcodes.ILOAD..Opcodes.ALOAD) a[i] = true
|
||||||
|
for (i in Opcodes.ISTORE..Opcodes.ASTORE) a[i] = true
|
||||||
|
|
||||||
|
// Relevant JumpInsnNode opcodes
|
||||||
|
a[Opcodes.GOTO] = true
|
||||||
|
a[Opcodes.IFNONNULL] = true
|
||||||
|
a[Opcodes.IFNULL] = true
|
||||||
|
for (i in Opcodes.IFEQ..Opcodes.IF_ACMPNE) a[i] = true
|
||||||
|
|
||||||
|
a[Opcodes.LOOKUPSWITCH] = true
|
||||||
|
a[Opcodes.TABLESWITCH] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("DuplicatedCode")
|
||||||
|
class FastStoreLoadAnalyzer<V : StoreLoadValue>(
|
||||||
|
private val owner: String,
|
||||||
|
private val method: MethodNode,
|
||||||
|
private val interpreter: StoreLoadInterpreter<V>
|
||||||
|
) {
|
||||||
|
private val insnsArray = method.instructions.toArray()
|
||||||
|
private val nInsns = method.instructions.size()
|
||||||
|
|
||||||
|
private val isMergeNode = BooleanArray(nInsns)
|
||||||
|
|
||||||
|
private val frames: Array<StoreLoadFrame<V>?> = arrayOfNulls(nInsns)
|
||||||
|
|
||||||
|
private val handlers: Array<MutableList<TryCatchBlockNode>?> = arrayOfNulls(nInsns)
|
||||||
|
private val queued = BooleanArray(nInsns)
|
||||||
|
private val queue = IntArray(nInsns)
|
||||||
|
private var top = 0
|
||||||
|
|
||||||
|
fun analyze(): Array<StoreLoadFrame<V>?> {
|
||||||
|
if (nInsns == 0) return frames
|
||||||
|
|
||||||
|
checkAssertions()
|
||||||
|
computeExceptionHandlersForEachInsn(method)
|
||||||
|
initMergeNodes()
|
||||||
|
|
||||||
|
val current = newFrame(method.maxLocals)
|
||||||
|
val handler = newFrame(method.maxLocals)
|
||||||
|
initLocals(current)
|
||||||
|
mergeControlFlowEdge(0, current)
|
||||||
|
|
||||||
|
while (top > 0) {
|
||||||
|
val insn = queue[--top]
|
||||||
|
val f = frames[insn]!!
|
||||||
|
queued[insn] = false
|
||||||
|
|
||||||
|
val insnNode = method.instructions[insn]
|
||||||
|
try {
|
||||||
|
val insnOpcode = insnNode.opcode
|
||||||
|
val insnType = insnNode.type
|
||||||
|
|
||||||
|
if (insnType == AbstractInsnNode.LABEL || insnType == AbstractInsnNode.LINE || insnType == AbstractInsnNode.FRAME) {
|
||||||
|
mergeControlFlowEdge(insn + 1, f)
|
||||||
|
} else {
|
||||||
|
current.init(f).execute(insnNode, interpreter)
|
||||||
|
when {
|
||||||
|
insnType == AbstractInsnNode.JUMP_INSN ->
|
||||||
|
visitJumpInsnNode(insnNode as JumpInsnNode, current, insn, insnOpcode)
|
||||||
|
insnType == AbstractInsnNode.LOOKUPSWITCH_INSN ->
|
||||||
|
visitLookupSwitchInsnNode(insnNode as LookupSwitchInsnNode, current)
|
||||||
|
insnType == AbstractInsnNode.TABLESWITCH_INSN ->
|
||||||
|
visitTableSwitchInsnNode(insnNode as TableSwitchInsnNode, current)
|
||||||
|
insnOpcode != Opcodes.ATHROW && (insnOpcode < Opcodes.IRETURN || insnOpcode > Opcodes.RETURN) ->
|
||||||
|
mergeControlFlowEdge(insn + 1, current)
|
||||||
|
else -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No need to analyze exception handler code for instructions that just propagate data flow information forward.
|
||||||
|
if (isRelevantNode(insn, insnOpcode)) {
|
||||||
|
handlers[insn]?.forEach { tcb ->
|
||||||
|
val jump = tcb.handler.indexOf()
|
||||||
|
handler.init(f)
|
||||||
|
mergeControlFlowEdge(jump, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: AnalyzerException) {
|
||||||
|
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(method.instructions)}: ${e.message}", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isRelevantNode(insn: Int, insnOpcode: Int) =
|
||||||
|
isMergeNode[insn] ||
|
||||||
|
insnOpcode >= 0 && isOpcodeRelevantForStoreLoadAnalysis[insnOpcode]
|
||||||
|
|
||||||
|
private fun newFrame(maxLocals: Int) =
|
||||||
|
StoreLoadFrame<V>(maxLocals)
|
||||||
|
|
||||||
|
private fun AbstractInsnNode.indexOf() =
|
||||||
|
method.instructions.indexOf(this)
|
||||||
|
|
||||||
|
private fun checkAssertions() {
|
||||||
|
if (insnsArray.any { it.opcode == Opcodes.JSR || it.opcode == Opcodes.RET })
|
||||||
|
throw AssertionError("Subroutines are deprecated since Java 6")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitTableSwitchInsnNode(insnNode: TableSwitchInsnNode, current: StoreLoadFrame<V>) {
|
||||||
|
mergeControlFlowEdge(insnNode.dflt.indexOf(), current)
|
||||||
|
for (label in insnNode.labels) {
|
||||||
|
mergeControlFlowEdge(label.indexOf(), current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitLookupSwitchInsnNode(insnNode: LookupSwitchInsnNode, current: StoreLoadFrame<V>) {
|
||||||
|
mergeControlFlowEdge(insnNode.dflt.indexOf(), current)
|
||||||
|
for (label in insnNode.labels) {
|
||||||
|
mergeControlFlowEdge(label.indexOf(), current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitJumpInsnNode(insnNode: JumpInsnNode, current: StoreLoadFrame<V>, insn: Int, insnOpcode: Int) {
|
||||||
|
if (insnOpcode != Opcodes.GOTO) {
|
||||||
|
mergeControlFlowEdge(insn + 1, current)
|
||||||
|
}
|
||||||
|
mergeControlFlowEdge(insnNode.label.indexOf(), current)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeExceptionHandlersForEachInsn(m: MethodNode) {
|
||||||
|
for (tcb in m.tryCatchBlocks) {
|
||||||
|
val begin = tcb.start.indexOf()
|
||||||
|
val end = tcb.end.indexOf()
|
||||||
|
for (j in begin until end) {
|
||||||
|
if (!insnsArray[j].isMeaningful) continue
|
||||||
|
var insnHandlers: MutableList<TryCatchBlockNode>? = handlers[j]
|
||||||
|
if (insnHandlers == null) {
|
||||||
|
insnHandlers = SmartList()
|
||||||
|
handlers[j] = insnHandlers
|
||||||
|
}
|
||||||
|
insnHandlers.add(tcb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initMergeNodes() {
|
||||||
|
for (insn in insnsArray) {
|
||||||
|
when (insn.type) {
|
||||||
|
AbstractInsnNode.JUMP_INSN -> {
|
||||||
|
val jumpInsn = insn as JumpInsnNode
|
||||||
|
isMergeNode[jumpInsn.label.indexOf()] = true
|
||||||
|
}
|
||||||
|
AbstractInsnNode.LOOKUPSWITCH_INSN -> {
|
||||||
|
val switchInsn = insn as LookupSwitchInsnNode
|
||||||
|
isMergeNode[switchInsn.dflt.indexOf()] = true
|
||||||
|
for (label in switchInsn.labels) {
|
||||||
|
isMergeNode[label.indexOf()] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AbstractInsnNode.TABLESWITCH_INSN -> {
|
||||||
|
val switchInsn = insn as TableSwitchInsnNode
|
||||||
|
isMergeNode[switchInsn.dflt.indexOf()] = true
|
||||||
|
for (label in switchInsn.labels) {
|
||||||
|
isMergeNode[label.indexOf()] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (tcb in method.tryCatchBlocks) {
|
||||||
|
isMergeNode[tcb.handler.indexOf()] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun initLocals(current: StoreLoadFrame<V>) {
|
||||||
|
val args = Type.getArgumentTypes(method.desc)
|
||||||
|
var local = 0
|
||||||
|
if ((method.access and Opcodes.ACC_STATIC) == 0) {
|
||||||
|
val ctype = Type.getObjectType(owner)
|
||||||
|
current[local++] = interpreter.valueParameter(ctype)
|
||||||
|
}
|
||||||
|
for (arg in args) {
|
||||||
|
current[local++] = interpreter.valueParameter(arg)
|
||||||
|
if (arg.size == 2) {
|
||||||
|
current[local++] = interpreter.uninitialized()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (local < method.maxLocals) {
|
||||||
|
current[local++] = interpreter.uninitialized()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mergeControlFlowEdge(dest: Int, frame: StoreLoadFrame<V>) {
|
||||||
|
val oldFrame = frames[dest]
|
||||||
|
val changes = when {
|
||||||
|
oldFrame == null -> {
|
||||||
|
frames[dest] = newFrame(frame.maxLocals).init(frame)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
!isMergeNode[dest] -> {
|
||||||
|
oldFrame.init(frame)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else ->
|
||||||
|
oldFrame.merge(frame, interpreter)
|
||||||
|
}
|
||||||
|
if (changes && !queued[dest]) {
|
||||||
|
queued[dest] = true
|
||||||
|
queue[top++] = dest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+103
-88
@@ -5,47 +5,17 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.optimization.temporaryVals
|
package org.jetbrains.kotlin.codegen.optimization.temporaryVals
|
||||||
|
|
||||||
import org.jetbrains.kotlin.codegen.inline.INLINE_MARKER_CLASS_NAME
|
import org.jetbrains.kotlin.codegen.optimization.OptimizationMethodVisitor
|
||||||
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.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.kotlin.utils.SmartSet
|
||||||
import org.jetbrains.org.objectweb.asm.Handle
|
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
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.AbstractInsnNode
|
||||||
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode
|
import org.jetbrains.org.objectweb.asm.tree.IincInsnNode
|
||||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||||
import org.jetbrains.org.objectweb.asm.tree.VarInsnNode
|
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:
|
// A temporary val is a local variables that is:
|
||||||
// - initialized once (with some xSTORE instruction)
|
// - initialized once (with some xSTORE instruction)
|
||||||
// - is not written by any other instruction (xSTORE or IINC)
|
// - is not written by any other instruction (xSTORE or IINC)
|
||||||
@@ -59,11 +29,68 @@ class TemporaryVal(
|
|||||||
class TemporaryValsAnalyzer {
|
class TemporaryValsAnalyzer {
|
||||||
|
|
||||||
fun analyze(internalClassName: String, methodNode: MethodNode): List<TemporaryVal> {
|
fun analyze(internalClassName: String, methodNode: MethodNode): List<TemporaryVal> {
|
||||||
val temporaryValStores = methodNode.instructions.filter { it.isStoreOperation() && it.previous.isTemporaryValInitMarker() }
|
val insnList = methodNode.instructions
|
||||||
if (temporaryValStores.isEmpty()) return emptyList()
|
val insnArray = insnList.toArray()
|
||||||
|
|
||||||
val storeInsnToStoreData = temporaryValStores.associateWith { StoreData(it) }
|
val potentiallyTemporaryStores = insnList.filterTo(LinkedHashSet()) { it.isStoreOperation() }
|
||||||
FastMethodAnalyzer(internalClassName, methodNode, StoreTrackingInterpreter(storeInsnToStoreData)).analyze()
|
|
||||||
|
for (lv in methodNode.localVariables) {
|
||||||
|
// Exclude stores within LVT entry liveness ranges.
|
||||||
|
for (i in insnList.indexOf(lv.start) until insnList.indexOf(lv.end)) {
|
||||||
|
val insn = insnArray[i]
|
||||||
|
if (insn.isStoreOperation() && (insn as VarInsnNode).`var` == lv.index) {
|
||||||
|
potentiallyTemporaryStores.remove(insn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove 1st store that would definitely be observed at local variable liveness start.
|
||||||
|
var p = lv.start.previous
|
||||||
|
while (p != null) {
|
||||||
|
if (p.isStoreOperation() && (p as VarInsnNode).`var` == lv.index) {
|
||||||
|
potentiallyTemporaryStores.remove(p)
|
||||||
|
break
|
||||||
|
} else if (
|
||||||
|
p.type == AbstractInsnNode.LABEL ||
|
||||||
|
p.opcode in Opcodes.IRETURN..Opcodes.RETURN ||
|
||||||
|
p.opcode == Opcodes.GOTO ||
|
||||||
|
p.opcode == Opcodes.ATHROW
|
||||||
|
) {
|
||||||
|
// Label might be a jump target;
|
||||||
|
// return/goto/throw instructions don't pass control to the next instruction.
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
p = p.previous
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the method is big, and we couldn't eliminate enough temporary variable store candidates,
|
||||||
|
// bail out, treat all variables as non-temporary.
|
||||||
|
// Here we estimate memory required to store all relevant information as O(N * M * K),
|
||||||
|
// N = number of method instructions
|
||||||
|
// M = number of local variables
|
||||||
|
// K = number of potential temporary variables so far
|
||||||
|
val memoryComplexity = methodNode.instructions.size().toLong() *
|
||||||
|
methodNode.localVariables.size *
|
||||||
|
potentiallyTemporaryStores.size /
|
||||||
|
(1024 * 1024)
|
||||||
|
if (memoryComplexity > OptimizationMethodVisitor.MEMORY_LIMIT_BY_METHOD_MB)
|
||||||
|
return emptyList()
|
||||||
|
|
||||||
|
val storeInsnToStoreData = potentiallyTemporaryStores.associateWith { StoreData(it) }
|
||||||
|
|
||||||
|
val frames = FastStoreLoadAnalyzer(internalClassName, methodNode, StoreTrackingInterpreter(storeInsnToStoreData)).analyze()
|
||||||
|
|
||||||
|
// Exclude stores observed at LVT liveness range start using information from bytecode analysis.
|
||||||
|
for (lv in methodNode.localVariables) {
|
||||||
|
val frameAtStart = frames[insnList.indexOf(lv.start)] ?: continue
|
||||||
|
when (val valueAtStart = frameAtStart[lv.index]) {
|
||||||
|
is StoredValue.Store ->
|
||||||
|
valueAtStart.temporaryVal.isDirty = true
|
||||||
|
is StoredValue.DirtyStore ->
|
||||||
|
valueAtStart.temporaryVals.forEach { it.isDirty = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return storeInsnToStoreData.values
|
return storeInsnToStoreData.values
|
||||||
.filterNot { it.isDirty }
|
.filterNot { it.isDirty }
|
||||||
@@ -72,34 +99,22 @@ class TemporaryValsAnalyzer {
|
|||||||
val loadInsns = it.loads.map { load -> load as VarInsnNode }
|
val loadInsns = it.loads.map { load -> load as VarInsnNode }
|
||||||
TemporaryVal(storeInsn.`var`, storeInsn, loadInsns)
|
TemporaryVal(storeInsn.`var`, storeInsn, loadInsns)
|
||||||
}
|
}
|
||||||
.sortedBy { methodNode.instructions.indexOf(it.storeInsn) }
|
.sortedBy { insnList.indexOf(it.storeInsn) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StoreData(val storeInsn: AbstractInsnNode) {
|
private class StoreData(val storeInsn: AbstractInsnNode) {
|
||||||
var isDirty = false
|
var isDirty = false
|
||||||
|
|
||||||
val storeOpcode = storeInsn.opcode
|
val value = StoredValue.Store(this)
|
||||||
|
|
||||||
val value =
|
|
||||||
StoredValue.Store(
|
|
||||||
this,
|
|
||||||
when (storeOpcode) {
|
|
||||||
Opcodes.LSTORE, Opcodes.DSTORE -> 2
|
|
||||||
else -> 1
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val loads = LinkedHashSet<AbstractInsnNode>()
|
val loads = LinkedHashSet<AbstractInsnNode>()
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class StoredValue(private val _size: Int) : Value {
|
private sealed class StoredValue : StoreLoadValue {
|
||||||
override fun getSize(): Int = _size
|
|
||||||
|
|
||||||
object Small : StoredValue(1)
|
object Unknown : StoredValue()
|
||||||
|
|
||||||
object Big : StoredValue(2)
|
class Store(val temporaryVal: StoreData) : StoredValue() {
|
||||||
|
|
||||||
class Store(val temporaryVal: StoreData, size: Int) : StoredValue(size) {
|
|
||||||
override fun equals(other: Any?): Boolean =
|
override fun equals(other: Any?): Boolean =
|
||||||
other is Store && other.temporaryVal === temporaryVal
|
other is Store && other.temporaryVal === temporaryVal
|
||||||
|
|
||||||
@@ -107,7 +122,7 @@ class TemporaryValsAnalyzer {
|
|||||||
temporaryVal.hashCode()
|
temporaryVal.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
class DirtyStore(val temporaryVals: Collection<StoreData>, size: Int) : StoredValue(size) {
|
class DirtyStore(val temporaryVals: Collection<StoreData>) : StoredValue() {
|
||||||
override fun equals(other: Any?): Boolean =
|
override fun equals(other: Any?): Boolean =
|
||||||
other is DirtyStore && other.temporaryVals == temporaryVals
|
other is DirtyStore && other.temporaryVals == temporaryVals
|
||||||
|
|
||||||
@@ -118,45 +133,45 @@ class TemporaryValsAnalyzer {
|
|||||||
|
|
||||||
private class StoreTrackingInterpreter(
|
private class StoreTrackingInterpreter(
|
||||||
private val storeInsnToStoreData: Map<AbstractInsnNode, StoreData>
|
private val storeInsnToStoreData: Map<AbstractInsnNode, StoreData>
|
||||||
) : BasicTypeInterpreter<StoredValue>() {
|
) : StoreLoadInterpreter<StoredValue> {
|
||||||
|
|
||||||
override fun uninitializedValue(): StoredValue = StoredValue.Small
|
override fun uninitialized(): StoredValue =
|
||||||
override fun booleanValue(): StoredValue = StoredValue.Small
|
StoredValue.Unknown
|
||||||
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 {
|
override fun valueParameter(type: Type): StoredValue =
|
||||||
if (insn.isStoreOperation()) {
|
StoredValue.Unknown
|
||||||
val temporaryValData = storeInsnToStoreData[insn]
|
|
||||||
if (temporaryValData != null) {
|
override fun store(insn: VarInsnNode): StoredValue {
|
||||||
return temporaryValData.value
|
val temporaryValData = storeInsnToStoreData[insn]
|
||||||
}
|
if (temporaryValData != null) {
|
||||||
} else if (insn.isLoadOperation()) {
|
return temporaryValData.value
|
||||||
if (value is StoredValue.DirtyStore) {
|
}
|
||||||
// If we load a dirty value, invalidate all related temporary vals.
|
return StoredValue.Unknown
|
||||||
for (temporaryValData in value.temporaryVals) {
|
}
|
||||||
temporaryValData.isDirty = true
|
|
||||||
}
|
override fun load(insn: VarInsnNode, value: StoredValue) {
|
||||||
} else if (value is StoredValue.Store) {
|
if (value is StoredValue.DirtyStore) {
|
||||||
// Keep track of a load instruction
|
// If we load a dirty value, invalidate all related temporary vals.
|
||||||
value.temporaryVal.loads.add(insn)
|
value.temporaryVals.forEach { it.isDirty = true }
|
||||||
|
} else if (value is StoredValue.Store) {
|
||||||
|
// Keep track of a load instruction
|
||||||
|
value.temporaryVal.loads.add(insn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun iinc(insn: IincInsnNode, value: StoredValue): StoredValue {
|
||||||
|
when (value) {
|
||||||
|
is StoredValue.Store ->
|
||||||
|
value.temporaryVal.isDirty = true
|
||||||
|
is StoredValue.DirtyStore ->
|
||||||
|
value.temporaryVals.forEach { it.isDirty = true }
|
||||||
|
else -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun merge(a: StoredValue, b: StoredValue): StoredValue {
|
override fun merge(a: StoredValue, b: StoredValue): StoredValue {
|
||||||
return when {
|
return when {
|
||||||
a === b ->
|
a === b ->
|
||||||
@@ -167,10 +182,10 @@ class TemporaryValsAnalyzer {
|
|||||||
// Loading such value invalidates all related temporary vals.
|
// Loading such value invalidates all related temporary vals.
|
||||||
val dirtySet = SmartSet.create(a.temporaryVals())
|
val dirtySet = SmartSet.create(a.temporaryVals())
|
||||||
dirtySet.addAll(b.temporaryVals())
|
dirtySet.addAll(b.temporaryVals())
|
||||||
StoredValue.DirtyStore(dirtySet, a.size)
|
StoredValue.DirtyStore(dirtySet)
|
||||||
}
|
}
|
||||||
else ->
|
else ->
|
||||||
StoredValue.Small
|
StoredValue.Unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -15,7 +15,7 @@ import org.jetbrains.org.objectweb.asm.Type
|
|||||||
import org.jetbrains.org.objectweb.asm.tree.*
|
import org.jetbrains.org.objectweb.asm.tree.*
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
class CodeCompactionTransformer : MethodTransformer() {
|
class TemporaryVariablesEliminationTransformer : MethodTransformer() {
|
||||||
private val temporaryValsAnalyzer = TemporaryValsAnalyzer()
|
private val temporaryValsAnalyzer = TemporaryValsAnalyzer()
|
||||||
private val deadCodeElimination = DeadCodeEliminationMethodTransformer()
|
private val deadCodeElimination = DeadCodeEliminationMethodTransformer()
|
||||||
|
|
||||||
@@ -32,7 +32,6 @@ class CodeCompactionTransformer : MethodTransformer() {
|
|||||||
optimizeTemporaryVals(cfg, temporaryVals)
|
optimizeTemporaryVals(cfg, temporaryVals)
|
||||||
}
|
}
|
||||||
|
|
||||||
methodNode.stripTemporaryValInitMarkers()
|
|
||||||
methodNode.removeUnusedLocalVariables()
|
methodNode.removeUnusedLocalVariables()
|
||||||
}
|
}
|
||||||
|
|
||||||
-4
@@ -28,7 +28,6 @@ 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
|
||||||
@@ -630,9 +629,6 @@ 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)
|
||||||
|
|||||||
+1
-1
@@ -12,4 +12,4 @@ suspend fun suspendThere(param: Int, param2: String, param3: Long): String {
|
|||||||
return a + b
|
return a + b
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1 ASTORE 4
|
// 0 ASTORE 4
|
||||||
|
|||||||
+1
-3
@@ -43,8 +43,6 @@ fun box(): String {
|
|||||||
|
|
||||||
// 1 LOCALVARIABLE i Ljava/lang/String; L.* 3
|
// 1 LOCALVARIABLE i Ljava/lang/String; L.* 3
|
||||||
// 1 PUTFIELD VarValueConflictsWithTableSameSortKt\$box\$1.L\$0 : Ljava/lang/Object;
|
// 1 PUTFIELD VarValueConflictsWithTableSameSortKt\$box\$1.L\$0 : Ljava/lang/Object;
|
||||||
/* 1 load in the catch (e: Throwable) { throw e } block which is implicitly wrapped around try/finally */
|
|
||||||
// 1 ALOAD 3\s+ATHROW
|
|
||||||
/* 1 load in result = s */
|
/* 1 load in result = s */
|
||||||
// 1 ALOAD 3\s+PUTFIELD kotlin/jvm/internal/Ref\$ObjectRef\.element
|
// 1 ALOAD 3\s+PUTFIELD kotlin/jvm/internal/Ref\$ObjectRef\.element
|
||||||
/* 1 load in spill */
|
/* 1 load in spill */
|
||||||
@@ -52,7 +50,7 @@ fun box(): String {
|
|||||||
/* 2 loads in println(s) */
|
/* 2 loads in println(s) */
|
||||||
// 2 ALOAD 3\s+INVOKEVIRTUAL java/io/PrintStream.println \(Ljava/lang/Object;\)V
|
// 2 ALOAD 3\s+INVOKEVIRTUAL java/io/PrintStream.println \(Ljava/lang/Object;\)V
|
||||||
/* But no further load when spilling 's' to the continuation */
|
/* But no further load when spilling 's' to the continuation */
|
||||||
// 5 ALOAD 3
|
// 4 ALOAD 3
|
||||||
|
|
||||||
// We merge LVT records for two consequent branches, but we split the local over the restore code.
|
// We merge LVT records for two consequent branches, but we split the local over the restore code.
|
||||||
// JVM_IR_TEMPLATES
|
// JVM_IR_TEMPLATES
|
||||||
|
|||||||
+2
-2
@@ -21,6 +21,6 @@ fun foo() : String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 12 ALOAD
|
// 11 ALOAD
|
||||||
// 2 ASTORE
|
// 1 ASTORE
|
||||||
// 0 InlineMarker
|
// 0 InlineMarker
|
||||||
|
|||||||
+2
-2
@@ -11,7 +11,7 @@ fun foo() : String {
|
|||||||
return foobar("abc", bar("ghi") { x -> x + "jkl" }, "mno")
|
return foobar("abc", bar("ghi") { x -> x + "jkl" }, "mno")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6 ASTORE
|
// 4 ASTORE
|
||||||
// 18 ALOAD
|
// 16 ALOAD
|
||||||
// 1 MAXLOCALS = 7
|
// 1 MAXLOCALS = 7
|
||||||
// 0 InlineMarker
|
// 0 InlineMarker
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ the Kotlin IntelliJ IDEA plugin:
|
|||||||
- License: BSD ([license/third_party/asm_license.txt][asm])
|
- License: BSD ([license/third_party/asm_license.txt][asm])
|
||||||
- Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom
|
- Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom
|
||||||
|
|
||||||
|
- Path: compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/FastStoreLoadAnalyzer.kt
|
||||||
|
- License: BSD ([license/third_party/asm_license.txt][asm])
|
||||||
|
- Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom
|
||||||
|
|
||||||
- Path: core/reflection.jvm/src/kotlin.reflect/jvm/internal/pcollections
|
- Path: core/reflection.jvm/src/kotlin.reflect/jvm/internal/pcollections
|
||||||
- License: MIT ([license/third_party/pcollections_LICENSE.txt][pcollections])
|
- License: MIT ([license/third_party/pcollections_LICENSE.txt][pcollections])
|
||||||
- Origin: Derived from PCollections, A Persistent Java Collections Library (https://pcollections.org/)
|
- Origin: Derived from PCollections, A Persistent Java Collections Library (https://pcollections.org/)
|
||||||
|
|||||||
Reference in New Issue
Block a user