JVM infer temporary vals from bytecode

This commit is contained in:
Dmitry Petrov
2021-08-20 17:20:59 +03:00
committed by TeamCityServer
parent a4e299b8e1
commit dcbc2ea2b3
10 changed files with 433 additions and 105 deletions
@@ -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 {
@@ -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
}
}
}
@@ -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
} }
} }
@@ -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()
} }
@@ -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)
@@ -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
@@ -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
@@ -21,6 +21,6 @@ fun foo() : String {
) )
} }
// 12 ALOAD // 11 ALOAD
// 2 ASTORE // 1 ASTORE
// 0 InlineMarker // 0 InlineMarker
@@ -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
+4
View File
@@ -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/)