diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt index 9a42d531c87..25bbec6d8cc 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt @@ -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 { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/FastStoreLoadAnalyzer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/FastStoreLoadAnalyzer.kt new file mode 100644 index 00000000000..b6b958556a5 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/FastStoreLoadAnalyzer.kt @@ -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 { + 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(val maxLocals: Int) { + private val locals = arrayOfNulls(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): StoreLoadFrame { + System.arraycopy(other.locals, 0, this.locals, 0, locals.size) + return this + } + + fun execute(insn: AbstractInsnNode, interpreter: StoreLoadInterpreter) { + 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, interpreter: StoreLoadInterpreter): 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( + private val owner: String, + private val method: MethodNode, + private val interpreter: StoreLoadInterpreter +) { + private val insnsArray = method.instructions.toArray() + private val nInsns = method.instructions.size() + + private val isMergeNode = BooleanArray(nInsns) + + private val frames: Array?> = arrayOfNulls(nInsns) + + private val handlers: Array?> = arrayOfNulls(nInsns) + private val queued = BooleanArray(nInsns) + private val queue = IntArray(nInsns) + private var top = 0 + + fun analyze(): Array?> { + 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(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) { + mergeControlFlowEdge(insnNode.dflt.indexOf(), current) + for (label in insnNode.labels) { + mergeControlFlowEdge(label.indexOf(), current) + } + } + + private fun visitLookupSwitchInsnNode(insnNode: LookupSwitchInsnNode, current: StoreLoadFrame) { + mergeControlFlowEdge(insnNode.dflt.indexOf(), current) + for (label in insnNode.labels) { + mergeControlFlowEdge(label.indexOf(), current) + } + } + + private fun visitJumpInsnNode(insnNode: JumpInsnNode, current: StoreLoadFrame, 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? = 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) { + 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) { + 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 + } + } + +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/TemporaryVals.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/TemporaryVals.kt index ebd6c4d20ad..bb358175814 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/TemporaryVals.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/TemporaryVals.kt @@ -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 = "" - -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 { - 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() } - 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, size: Int) : StoredValue(size) { + class DirtyStore(val temporaryVals: Collection) : 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 - ) : BasicTypeInterpreter() { + ) : StoreLoadInterpreter { - 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()) { - val temporaryValData = storeInsnToStoreData[insn] - if (temporaryValData != null) { - return temporaryValData.value - } - } else if (insn.isLoadOperation()) { - if (value is StoredValue.DirtyStore) { - // If we load a dirty value, invalidate all related temporary vals. - for (temporaryValData in value.temporaryVals) { - temporaryValData.isDirty = true - } - } else if (value is StoredValue.Store) { - // Keep track of a load instruction - value.temporaryVal.loads.add(insn) + override fun valueParameter(type: Type): StoredValue = + StoredValue.Unknown + + override fun store(insn: VarInsnNode): StoredValue { + val temporaryValData = storeInsnToStoreData[insn] + if (temporaryValData != null) { + return temporaryValData.value + } + 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. + 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 } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/CodeCompactionTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/TemporaryVariablesEliminationTransformer.kt similarity index 99% rename from compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/CodeCompactionTransformer.kt rename to compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/TemporaryVariablesEliminationTransformer.kt index 89f4cbd09e1..56eb070fb57 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/CodeCompactionTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/temporaryVals/TemporaryVariablesEliminationTransformer.kt @@ -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() } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index 77f87f4e82d..72c0f2e4a6e 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -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) diff --git a/compiler/testData/codegen/bytecodeText/coroutines/doNotReassignContinuation.kt b/compiler/testData/codegen/bytecodeText/coroutines/doNotReassignContinuation.kt index 239a7bf0f76..2f4eea71246 100644 --- a/compiler/testData/codegen/bytecodeText/coroutines/doNotReassignContinuation.kt +++ b/compiler/testData/codegen/bytecodeText/coroutines/doNotReassignContinuation.kt @@ -12,4 +12,4 @@ suspend fun suspendThere(param: Int, param2: String, param3: Long): String { return a + b } -// 1 ASTORE 4 +// 0 ASTORE 4 diff --git a/compiler/testData/codegen/bytecodeText/coroutines/varValueConflictsWithTableSameSort.kt b/compiler/testData/codegen/bytecodeText/coroutines/varValueConflictsWithTableSameSort.kt index 98890b07764..5a0cfdb5d8b 100644 --- a/compiler/testData/codegen/bytecodeText/coroutines/varValueConflictsWithTableSameSort.kt +++ b/compiler/testData/codegen/bytecodeText/coroutines/varValueConflictsWithTableSameSort.kt @@ -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 diff --git a/compiler/testData/codegen/bytecodeText/storeStackBeforeInline/unreachableMarker.kt b/compiler/testData/codegen/bytecodeText/storeStackBeforeInline/unreachableMarker.kt index ece1e46f660..5def356c94c 100644 --- a/compiler/testData/codegen/bytecodeText/storeStackBeforeInline/unreachableMarker.kt +++ b/compiler/testData/codegen/bytecodeText/storeStackBeforeInline/unreachableMarker.kt @@ -21,6 +21,6 @@ fun foo() : String { ) } -// 12 ALOAD -// 2 ASTORE +// 11 ALOAD +// 1 ASTORE // 0 InlineMarker diff --git a/compiler/testData/codegen/bytecodeText/storeStackBeforeInline/withLambda.kt b/compiler/testData/codegen/bytecodeText/storeStackBeforeInline/withLambda.kt index 5568b8ae2a8..3698f673dc4 100644 --- a/compiler/testData/codegen/bytecodeText/storeStackBeforeInline/withLambda.kt +++ b/compiler/testData/codegen/bytecodeText/storeStackBeforeInline/withLambda.kt @@ -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 diff --git a/license/README.md b/license/README.md index e31904dd6be..fac8341277a 100644 --- a/license/README.md +++ b/license/README.md @@ -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/)