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.common.prepareForEmitting
|
||||
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.state.GenerationState
|
||||
import org.jetbrains.org.objectweb.asm.MethodVisitor
|
||||
@@ -48,12 +48,12 @@ class OptimizationMethodVisitor(
|
||||
)
|
||||
|
||||
val optimizationTransformer = CompositeMethodTransformer(
|
||||
CodeCompactionTransformer(),
|
||||
CapturedVarsOptimizationMethodTransformer(),
|
||||
RedundantNullCheckMethodTransformer(generationState),
|
||||
RedundantCheckCastEliminationMethodTransformer(),
|
||||
ConstantConditionEliminationMethodTransformer(),
|
||||
RedundantBoxingMethodTransformer(generationState),
|
||||
TemporaryVariablesEliminationTransformer(),
|
||||
StackPeepholeOptimizationsTransformer(),
|
||||
PopBackwardPropagationTransformer(),
|
||||
DeadCodeEliminationMethodTransformer(),
|
||||
@@ -76,7 +76,7 @@ class OptimizationMethodVisitor(
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+94
-79
@@ -5,47 +5,17 @@
|
||||
|
||||
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.OptimizationMethodVisitor
|
||||
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.IincInsnNode
|
||||
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)
|
||||
@@ -59,11 +29,68 @@ class TemporaryVal(
|
||||
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 insnList = methodNode.instructions
|
||||
val insnArray = insnList.toArray()
|
||||
|
||||
val storeInsnToStoreData = temporaryValStores.associateWith { StoreData(it) }
|
||||
FastMethodAnalyzer(internalClassName, methodNode, StoreTrackingInterpreter(storeInsnToStoreData)).analyze()
|
||||
val potentiallyTemporaryStores = insnList.filterTo(LinkedHashSet()) { it.isStoreOperation() }
|
||||
|
||||
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
|
||||
.filterNot { it.isDirty }
|
||||
@@ -72,34 +99,22 @@ class TemporaryValsAnalyzer {
|
||||
val loadInsns = it.loads.map { load -> load as VarInsnNode }
|
||||
TemporaryVal(storeInsn.`var`, storeInsn, loadInsns)
|
||||
}
|
||||
.sortedBy { methodNode.instructions.indexOf(it.storeInsn) }
|
||||
.sortedBy { insnList.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 value = StoredValue.Store(this)
|
||||
|
||||
val loads = LinkedHashSet<AbstractInsnNode>()
|
||||
}
|
||||
|
||||
private sealed class StoredValue(private val _size: Int) : Value {
|
||||
override fun getSize(): Int = _size
|
||||
private sealed class StoredValue : StoreLoadValue {
|
||||
|
||||
object Small : StoredValue(1)
|
||||
object Unknown : StoredValue()
|
||||
|
||||
object Big : StoredValue(2)
|
||||
|
||||
class Store(val temporaryVal: StoreData, size: Int) : StoredValue(size) {
|
||||
class Store(val temporaryVal: StoreData) : StoredValue() {
|
||||
override fun equals(other: Any?): Boolean =
|
||||
other is Store && other.temporaryVal === temporaryVal
|
||||
|
||||
@@ -107,7 +122,7 @@ class TemporaryValsAnalyzer {
|
||||
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 =
|
||||
other is DirtyStore && other.temporaryVals == temporaryVals
|
||||
|
||||
@@ -118,45 +133,45 @@ class TemporaryValsAnalyzer {
|
||||
|
||||
private class StoreTrackingInterpreter(
|
||||
private val storeInsnToStoreData: Map<AbstractInsnNode, StoreData>
|
||||
) : BasicTypeInterpreter<StoredValue>() {
|
||||
) : StoreLoadInterpreter<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 uninitialized(): StoredValue =
|
||||
StoredValue.Unknown
|
||||
|
||||
override fun copyOperation(insn: AbstractInsnNode, value: StoredValue): StoredValue {
|
||||
if (insn.isStoreOperation()) {
|
||||
override fun valueParameter(type: Type): StoredValue =
|
||||
StoredValue.Unknown
|
||||
|
||||
override fun store(insn: VarInsnNode): StoredValue {
|
||||
val temporaryValData = storeInsnToStoreData[insn]
|
||||
if (temporaryValData != null) {
|
||||
return temporaryValData.value
|
||||
}
|
||||
} else if (insn.isLoadOperation()) {
|
||||
return StoredValue.Unknown
|
||||
}
|
||||
|
||||
override fun load(insn: VarInsnNode, value: StoredValue) {
|
||||
if (value is StoredValue.DirtyStore) {
|
||||
// If we load a dirty value, invalidate all related temporary vals.
|
||||
for (temporaryValData in value.temporaryVals) {
|
||||
temporaryValData.isDirty = true
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
override fun merge(a: StoredValue, b: StoredValue): StoredValue {
|
||||
return when {
|
||||
a === b ->
|
||||
@@ -167,10 +182,10 @@ class TemporaryValsAnalyzer {
|
||||
// Loading such value invalidates all related temporary vals.
|
||||
val dirtySet = SmartSet.create(a.temporaryVals())
|
||||
dirtySet.addAll(b.temporaryVals())
|
||||
StoredValue.DirtyStore(dirtySet, a.size)
|
||||
StoredValue.DirtyStore(dirtySet)
|
||||
}
|
||||
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 kotlin.math.max
|
||||
|
||||
class CodeCompactionTransformer : MethodTransformer() {
|
||||
class TemporaryVariablesEliminationTransformer : MethodTransformer() {
|
||||
private val temporaryValsAnalyzer = TemporaryValsAnalyzer()
|
||||
private val deadCodeElimination = DeadCodeEliminationMethodTransformer()
|
||||
|
||||
@@ -32,7 +32,6 @@ class CodeCompactionTransformer : MethodTransformer() {
|
||||
optimizeTemporaryVals(cfg, temporaryVals)
|
||||
}
|
||||
|
||||
methodNode.stripTemporaryValInitMarkers()
|
||||
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.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
|
||||
@@ -630,9 +629,6 @@ 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)
|
||||
|
||||
+1
-1
@@ -12,4 +12,4 @@ suspend fun suspendThere(param: Int, param2: String, param3: Long): String {
|
||||
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 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 ALOAD 3\s+PUTFIELD kotlin/jvm/internal/Ref\$ObjectRef\.element
|
||||
/* 1 load in spill */
|
||||
@@ -52,7 +50,7 @@ fun box(): String {
|
||||
/* 2 loads in println(s) */
|
||||
// 2 ALOAD 3\s+INVOKEVIRTUAL java/io/PrintStream.println \(Ljava/lang/Object;\)V
|
||||
/* 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.
|
||||
// JVM_IR_TEMPLATES
|
||||
|
||||
+2
-2
@@ -21,6 +21,6 @@ fun foo() : String {
|
||||
)
|
||||
}
|
||||
|
||||
// 12 ALOAD
|
||||
// 2 ASTORE
|
||||
// 11 ALOAD
|
||||
// 1 ASTORE
|
||||
// 0 InlineMarker
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ fun foo() : String {
|
||||
return foobar("abc", bar("ghi") { x -> x + "jkl" }, "mno")
|
||||
}
|
||||
|
||||
// 6 ASTORE
|
||||
// 18 ALOAD
|
||||
// 4 ASTORE
|
||||
// 16 ALOAD
|
||||
// 1 MAXLOCALS = 7
|
||||
// 0 InlineMarker
|
||||
|
||||
@@ -31,6 +31,10 @@ the Kotlin IntelliJ IDEA plugin:
|
||||
- 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: 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
|
||||
- License: MIT ([license/third_party/pcollections_LICENSE.txt][pcollections])
|
||||
- Origin: Derived from PCollections, A Persistent Java Collections Library (https://pcollections.org/)
|
||||
|
||||
Reference in New Issue
Block a user