JVM FixStackValue & FixStackInterpreter

This commit is contained in:
Dmitry Petrov
2021-07-19 18:12:27 +03:00
committed by teamcityserver
parent 8eb7e719d3
commit 3ecd612ce7
5 changed files with 227 additions and 45 deletions
@@ -21,7 +21,6 @@ import org.jetbrains.kotlin.codegen.inline.isAfterInlineMarker
import org.jetbrains.kotlin.codegen.inline.isBeforeInlineMarker
import org.jetbrains.kotlin.codegen.inline.isMarkedReturn
import org.jetbrains.kotlin.codegen.optimization.common.FlexibleMethodAnalyzer
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.org.objectweb.asm.Opcodes
@@ -30,7 +29,6 @@ import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode
import org.jetbrains.org.objectweb.asm.tree.LabelNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter
import kotlin.math.max
@@ -50,9 +48,14 @@ internal class FixStackAnalyzer(
val maxExtraStackSize: Int get() = analyzer.maxExtraStackSize
fun getStackToSpill(location: AbstractInsnNode) = analyzer.spilledStacks[location]
fun getActualStack(location: AbstractInsnNode) = getFrame(location)?.getStackContent()
fun getActualStackSize(location: AbstractInsnNode) = getFrame(location)?.stackSizeWithExtra ?: DEAD_CODE_STACK_SIZE
fun getStackToSpill(location: AbstractInsnNode): List<FixStackValue>? =
analyzer.spilledStacks[location]
fun getActualStack(location: AbstractInsnNode): List<FixStackValue>? =
getFrame(location)?.getStackContent()
fun getActualStackSize(location: AbstractInsnNode): Int =
getFrame(location)?.stackSizeWithExtra ?: DEAD_CODE_STACK_SIZE
fun getExpectedStackSize(location: AbstractInsnNode): Int {
// We should look for expected stack size at loop entry point markers if available,
@@ -90,9 +93,9 @@ internal class FixStackAnalyzer(
private val analyzer = InternalAnalyzer(owner)
private inner class InternalAnalyzer(owner: String) :
FlexibleMethodAnalyzer<BasicValue>(owner, method, OptimizationBasicInterpreter()) {
FlexibleMethodAnalyzer<FixStackValue>(owner, method, FixStackInterpreter()) {
val spilledStacks = hashMapOf<AbstractInsnNode, List<BasicValue>>()
val spilledStacks = hashMapOf<AbstractInsnNode, List<FixStackValue>>()
var maxExtraStackSize = 0; private set
override fun visitControlFlowEdge(insn: Int, successor: Int): Boolean {
@@ -101,15 +104,15 @@ internal class FixStackAnalyzer(
return !(insnNode is JumpInsnNode && context.breakContinueGotoNodes.contains(insnNode))
}
override fun newFrame(nLocals: Int, nStack: Int): Frame<BasicValue> =
override fun newFrame(nLocals: Int, nStack: Int): Frame<FixStackValue> =
FixStackFrame(nLocals, nStack)
private fun indexOf(node: AbstractInsnNode) = method.instructions.indexOf(node)
inner class FixStackFrame(nLocals: Int, nStack: Int) : Frame<BasicValue>(nLocals, nStack) {
val extraStack = Stack<BasicValue>()
inner class FixStackFrame(nLocals: Int, nStack: Int) : Frame<FixStackValue>(nLocals, nStack) {
val extraStack = Stack<FixStackValue>()
override fun init(src: Frame<out BasicValue>): Frame<BasicValue> {
override fun init(src: Frame<out FixStackValue>): Frame<FixStackValue> {
extraStack.clear()
extraStack.addAll((src as FixStackFrame).extraStack)
return super.init(src)
@@ -120,7 +123,7 @@ internal class FixStackAnalyzer(
super.clearStack()
}
override fun execute(insn: AbstractInsnNode, interpreter: Interpreter<BasicValue>) {
override fun execute(insn: AbstractInsnNode, interpreter: Interpreter<FixStackValue>) {
when {
PseudoInsn.SAVE_STACK_BEFORE_TRY.isa(insn) ->
executeSaveStackBeforeTry(insn)
@@ -141,14 +144,14 @@ internal class FixStackAnalyzer(
val stackSizeWithExtra: Int get() = super.getStackSize() + extraStack.size
fun getStackContent(): List<BasicValue> {
val savedStack = arrayListOf<BasicValue>()
fun getStackContent(): List<FixStackValue> {
val savedStack = ArrayList<FixStackValue>()
IntRange(0, super.getStackSize() - 1).mapTo(savedStack) { super.getStack(it) }
savedStack.addAll(extraStack)
return savedStack
}
override fun push(value: BasicValue) {
override fun push(value: FixStackValue?) {
if (super.getStackSize() < maxStackSize) {
super.push(value)
} else {
@@ -157,19 +160,18 @@ internal class FixStackAnalyzer(
}
}
fun pushAll(values: Collection<BasicValue>) {
fun pushAll(values: Collection<FixStackValue>) {
values.forEach { push(it) }
}
override fun pop(): BasicValue {
return if (extraStack.isNotEmpty()) {
override fun pop(): FixStackValue? =
if (extraStack.isNotEmpty()) {
extraStack.pop()
} else {
super.pop()
}
}
override fun setStack(i: Int, value: BasicValue) {
override fun setStack(i: Int, value: FixStackValue) {
if (i < super.getMaxStackSize()) {
super.setStack(i, value)
} else {
@@ -177,7 +179,7 @@ internal class FixStackAnalyzer(
}
}
override fun merge(frame: Frame<out BasicValue>, interpreter: Interpreter<BasicValue>): Boolean {
override fun merge(frame: Frame<out FixStackValue>, interpreter: Interpreter<FixStackValue>): Boolean {
val other = frame as FixStackFrame
if (stackSizeWithExtra != other.stackSizeWithExtra) {
throw AnalyzerException(null, "Incompatible stack heights")
@@ -0,0 +1,151 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.codegen.optimization.fixStack
import org.jetbrains.kotlin.codegen.inline.insnOpcodeText
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.tree.*
import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter
open class FixStackInterpreter : Interpreter<FixStackValue>(API_VERSION) {
override fun newValue(type: Type?): FixStackValue? =
type?.toFixStackValue()
override fun newOperation(insn: AbstractInsnNode): FixStackValue? =
when (insn.opcode) {
ACONST_NULL ->
FixStackValue.OBJECT
ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5 ->
FixStackValue.INT
LCONST_0, LCONST_1 ->
FixStackValue.LONG
FCONST_0, FCONST_1, FCONST_2 ->
FixStackValue.FLOAT
DCONST_0, DCONST_1 ->
FixStackValue.DOUBLE
BIPUSH, SIPUSH ->
FixStackValue.INT
LDC -> {
val cst = (insn as LdcInsnNode).cst
if (cst is Int) {
FixStackValue.INT
} else if (cst is Float) {
FixStackValue.FLOAT
} else if (cst is Long) {
FixStackValue.LONG
} else if (cst is Double) {
FixStackValue.DOUBLE
} else if (cst is String) {
FixStackValue.OBJECT
} else if (cst is Type) {
val sort = cst.sort
if (sort == Type.OBJECT || sort == Type.ARRAY || sort == Type.METHOD) {
FixStackValue.OBJECT
} else {
throw IllegalArgumentException("Illegal LDC constant $cst")
}
} else if (cst is Handle) {
FixStackValue.OBJECT
} else {
throw IllegalArgumentException("Illegal LDC constant $cst")
}
}
GETSTATIC ->
newValue(Type.getType((insn as FieldInsnNode).desc))
NEW ->
newValue(Type.getObjectType((insn as TypeInsnNode).desc))
else ->
throw IllegalArgumentException("Unexpected instruction: " + insn.insnOpcodeText)
}
override fun copyOperation(insn: AbstractInsnNode, value: FixStackValue?): FixStackValue? =
value
override fun binaryOperation(insn: AbstractInsnNode, value1: FixStackValue?, value2: FixStackValue?): FixStackValue? =
when (insn.opcode) {
IALOAD, BALOAD, CALOAD, SALOAD, IADD, ISUB, IMUL, IDIV, IREM, ISHL, ISHR, IUSHR, IAND, IOR, IXOR ->
FixStackValue.INT
FALOAD, FADD, FSUB, FMUL, FDIV, FREM ->
FixStackValue.FLOAT
LALOAD, LADD, LSUB, LMUL, LDIV, LREM, LSHL, LSHR, LUSHR, LAND, LOR, LXOR ->
FixStackValue.LONG
DALOAD, DADD, DSUB, DMUL, DDIV, DREM ->
FixStackValue.DOUBLE
AALOAD ->
FixStackValue.OBJECT
LCMP, FCMPL, FCMPG, DCMPL, DCMPG ->
FixStackValue.INT
IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, PUTFIELD ->
null
else ->
throw IllegalArgumentException("Unexpected instruction: " + insn.insnOpcodeText)
}
override fun ternaryOperation(
insn: AbstractInsnNode,
value1: FixStackValue?,
value2: FixStackValue?,
value3: FixStackValue?
): FixStackValue? =
null
override fun naryOperation(insn: AbstractInsnNode, values: List<FixStackValue?>): FixStackValue? =
when (insn.opcode) {
MULTIANEWARRAY ->
newValue(Type.getType((insn as MultiANewArrayInsnNode).desc))
INVOKEDYNAMIC ->
newValue(Type.getReturnType((insn as InvokeDynamicInsnNode).desc))
else ->
newValue(Type.getReturnType((insn as MethodInsnNode).desc))
}
override fun returnOperation(insn: AbstractInsnNode?, value: FixStackValue?, expected: FixStackValue?) {
}
override fun unaryOperation(insn: AbstractInsnNode, value: FixStackValue?): FixStackValue? =
when (insn.opcode) {
INEG, IINC, L2I, F2I, D2I, I2B, I2C, I2S ->
FixStackValue.INT
FNEG, I2F, L2F, D2F ->
FixStackValue.FLOAT
LNEG, I2L, F2L, D2L ->
FixStackValue.LONG
DNEG, I2D, L2D, F2D ->
FixStackValue.DOUBLE
IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, TABLESWITCH, LOOKUPSWITCH, IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, PUTSTATIC ->
null
GETFIELD ->
newValue(Type.getType((insn as FieldInsnNode).desc))
NEWARRAY ->
FixStackValue.OBJECT
ANEWARRAY -> {
FixStackValue.OBJECT
}
ARRAYLENGTH ->
FixStackValue.INT
ATHROW ->
null
CHECKCAST ->
FixStackValue.OBJECT
INSTANCEOF ->
FixStackValue.INT
MONITORENTER, MONITOREXIT, IFNULL, IFNONNULL ->
null
else ->
throw IllegalArgumentException("Unexpected instruction: " + insn.insnOpcodeText)
}
override fun merge(v: FixStackValue?, w: FixStackValue?): FixStackValue? =
when {
v == w -> v
v == null -> w
w == null -> v
else -> throw AssertionError("Mismatching value kinds: $v != $w")
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.codegen.optimization.fixStack
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.analysis.Value
enum class FixStackValue(
private val _size: Int,
val loadOpcode: Int,
val storeOpcode: Int,
val popOpcode: Int
) : Value {
INT(1, Opcodes.ILOAD, Opcodes.ISTORE, Opcodes.POP),
LONG(2, Opcodes.LLOAD, Opcodes.LSTORE, Opcodes.POP2),
FLOAT(1, Opcodes.FLOAD, Opcodes.FSTORE, Opcodes.POP),
DOUBLE(2, Opcodes.DLOAD, Opcodes.DSTORE, Opcodes.POP2),
OBJECT(1, Opcodes.ALOAD, Opcodes.ASTORE, Opcodes.POP)
;
override fun getSize(): Int = _size
}
fun Type.toFixStackValue(): FixStackValue? =
when (this.sort) {
Type.VOID -> null
Type.BOOLEAN, Type.BYTE, Type.CHAR, Type.SHORT, Type.INT -> FixStackValue.INT
Type.LONG -> FixStackValue.LONG
Type.FLOAT -> FixStackValue.FLOAT
Type.DOUBLE -> FixStackValue.DOUBLE
Type.OBJECT, Type.ARRAY, Type.METHOD -> FixStackValue.OBJECT
else -> throw AssertionError("Unexpected type: $this")
}
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.codegen.optimization.fixStack
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
import kotlin.math.max
internal class LocalVariablesManager(val context: FixStackContext, val methodNode: MethodNode) {
@@ -39,7 +38,10 @@ internal class LocalVariablesManager(val context: FixStackContext, val methodNod
methodNode.maxLocals = max(methodNode.maxLocals, newValue)
}
fun allocateVariablesForSaveStackMarker(saveStackMarker: AbstractInsnNode, savedStackValues: List<BasicValue>): SavedStackDescriptor {
fun allocateVariablesForSaveStackMarker(
saveStackMarker: AbstractInsnNode,
savedStackValues: List<FixStackValue>
): SavedStackDescriptor {
val numRestoreStackMarkers = context.restoreStackMarkersForSaveMarker[saveStackMarker]!!.size
return allocateNewHandle(numRestoreStackMarkers, saveStackMarker, savedStackValues)
}
@@ -47,12 +49,8 @@ internal class LocalVariablesManager(val context: FixStackContext, val methodNod
private fun allocateNewHandle(
numRestoreStackMarkers: Int,
saveStackMarker: AbstractInsnNode,
savedStackValues: List<BasicValue>
savedStackValues: List<FixStackValue>
): SavedStackDescriptor {
if (savedStackValues.any { it.type == null }) {
throw AssertionError("Uninitialized value on stack at ${methodNode.instructions.indexOf(saveStackMarker)}")
}
val firstUnusedLocalVarIndex = getFirstUnusedLocalVariableIndex()
val savedStackDescriptor = SavedStackDescriptor(savedStackValues, firstUnusedLocalVarIndex)
updateMaxLocals(savedStackDescriptor.firstUnusedLocalVarIndex)
@@ -78,7 +76,7 @@ internal class LocalVariablesManager(val context: FixStackContext, val methodNod
fun allocateVariablesForBeforeInlineMarker(
beforeInlineMarker: AbstractInsnNode,
savedStackValues: List<BasicValue>
savedStackValues: List<FixStackValue>
): SavedStackDescriptor {
return allocateNewHandle(1, beforeInlineMarker, savedStackValues)
}
@@ -101,7 +99,7 @@ internal class LocalVariablesManager(val context: FixStackContext, val methodNod
}
}
fun createReturnValueVariable(returnValue: BasicValue): Int {
fun createReturnValueVariable(returnValue: FixStackValue): Int {
val returnValueIndex = getFirstUnusedLocalVariableIndex()
updateMaxLocals(returnValueIndex + returnValue.size)
return returnValueIndex
@@ -54,10 +54,10 @@ fun <V : Value> Frame<V>.peekWords(size1: Int, size2: Int): List<V>? {
}
class SavedStackDescriptor(
val savedValues: List<BasicValue>,
val savedValues: List<FixStackValue>,
val firstLocalVarIndex: Int
) {
private val savedValuesSize = savedValues.fold(0, { size, value -> size + value.size })
private val savedValuesSize = savedValues.fold(0) { size, value -> size + value.size }
val firstUnusedLocalVarIndex = firstLocalVarIndex + savedValuesSize
override fun toString(): String =
@@ -88,13 +88,13 @@ fun restoreStackWithReturnValue(
methodNode: MethodNode,
nodeToReplace: AbstractInsnNode,
savedStackDescriptor: SavedStackDescriptor,
returnValue: BasicValue,
returnValue: FixStackValue,
returnValueLocalVarIndex: Int
) {
with(methodNode.instructions) {
insertBefore(nodeToReplace, VarInsnNode(returnValue.type.getOpcode(Opcodes.ISTORE), returnValueLocalVarIndex))
insertBefore(nodeToReplace, VarInsnNode(returnValue.storeOpcode, returnValueLocalVarIndex))
generateLoadInstructions(methodNode, nodeToReplace, savedStackDescriptor)
insertBefore(nodeToReplace, VarInsnNode(returnValue.type.getOpcode(Opcodes.ILOAD), returnValueLocalVarIndex))
insertBefore(nodeToReplace, VarInsnNode(returnValue.loadOpcode, returnValueLocalVarIndex))
remove(nodeToReplace)
}
}
@@ -102,10 +102,7 @@ fun restoreStackWithReturnValue(
fun generateLoadInstructions(methodNode: MethodNode, location: AbstractInsnNode, savedStackDescriptor: SavedStackDescriptor) {
var localVarIndex = savedStackDescriptor.firstLocalVarIndex
for (value in savedStackDescriptor.savedValues) {
methodNode.instructions.insertBefore(
location,
VarInsnNode(value.type.getOpcode(Opcodes.ILOAD), localVarIndex)
)
methodNode.instructions.insertBefore(location, VarInsnNode(value.loadOpcode, localVarIndex))
localVarIndex += value.size
}
}
@@ -114,10 +111,7 @@ fun generateStoreInstructions(methodNode: MethodNode, location: AbstractInsnNode
var localVarIndex = savedStackDescriptor.firstUnusedLocalVarIndex
for (value in savedStackDescriptor.savedValues.asReversed()) {
localVarIndex -= value.size
methodNode.instructions.insertBefore(
location,
VarInsnNode(value.type.getOpcode(Opcodes.ISTORE), localVarIndex)
)
methodNode.instructions.insertBefore(location, VarInsnNode(value.storeOpcode, localVarIndex))
}
}
@@ -146,10 +140,10 @@ fun replaceAlwaysTrueIfeqWithGoto(methodNode: MethodNode, node: AbstractInsnNode
}
}
fun replaceMarkerWithPops(methodNode: MethodNode, node: AbstractInsnNode, expectedStackSize: Int, stackContent: List<BasicValue>) {
fun replaceMarkerWithPops(methodNode: MethodNode, node: AbstractInsnNode, expectedStackSize: Int, stackContent: List<FixStackValue>) {
with(methodNode.instructions) {
for (stackValue in stackContent.subList(expectedStackSize, stackContent.size)) {
insert(node, getPopInstruction(stackValue))
insert(node, InsnNode(stackValue.popOpcode))
}
remove(node)
}