For all int-like typed variables, use int as field type and coerce
it during spill-unspill. Coerce int to boolean, otherwise, VerifyError is thrown on android Completely rewrite SpilledVariableFieldTypesAnalysis... again, but this time use BasicInterpreter This way, the analysis both does not use SourceInterpreter and is in line with the rest on analyses.
This commit is contained in:
+1
-1
@@ -647,7 +647,7 @@ class CoroutineTransformerMethodVisitor(
|
||||
}
|
||||
|
||||
for ((index, basicValue) in variablesToSpill) {
|
||||
if (basicValue.type == NULL_TYPE) {
|
||||
if (basicValue == StrictBasicValue.NULL_VALUE) {
|
||||
postponedActions.add {
|
||||
with(instructions) {
|
||||
insert(suspension.tryCatchBlockEndLabelAfterSuspensionCall, withInstructionAdapter {
|
||||
|
||||
+110
-319
@@ -5,365 +5,156 @@
|
||||
|
||||
package org.jetbrains.kotlin.codegen.coroutines
|
||||
|
||||
import org.jetbrains.kotlin.codegen.inline.insnOpcodeText
|
||||
import org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline
|
||||
import org.jetbrains.kotlin.codegen.StackValue
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.MethodAnalyzer
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes.*
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
|
||||
import org.jetbrains.org.objectweb.asm.Label
|
||||
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.*
|
||||
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 org.jetbrains.org.objectweb.asm.tree.analysis.Value
|
||||
|
||||
// BasicValue interpreter from ASM does not distinct 'int' types from other int-like types like 'byte' or 'boolean',
|
||||
// neither do HotSpot and JVM spec.
|
||||
// But it seems like Dalvik does not follow it, and spilling boolean value into an 'int' field fails with VerifyError on Android 4,
|
||||
// so this function calculates refined frames' markup.
|
||||
// Note that type of some values is only possible to determine by their usages (e.g. ICONST_1, BALOAD both may push boolean or byte on stack)
|
||||
// In this case, update the type of the value.
|
||||
// In this case, coerce the type of the value.
|
||||
|
||||
// StrictBasicValue with mutable type
|
||||
internal open class SpilledVariableFieldTypeValue(open var type: Type?, val insn: AbstractInsnNode?) : Value {
|
||||
override fun getSize(): Int = type?.size ?: 1
|
||||
internal class IloadedValue(val insns: Set<VarInsnNode>) : BasicValue(Type.INT_TYPE)
|
||||
|
||||
override fun equals(other: Any?): Boolean = other is SpilledVariableFieldTypeValue && type == other.type && insn == other.insn
|
||||
private class IntLikeCoerceInterpreter : OptimizationBasicInterpreter() {
|
||||
val needsToBeCoerced = mutableMapOf<VarInsnNode, Type>()
|
||||
|
||||
override fun hashCode(): Int = (type?.hashCode() ?: 0) xor insn.hashCode()
|
||||
private fun coerce(value: IloadedValue, type: Type) {
|
||||
for (insn in value.insns) {
|
||||
needsToBeCoerced[insn] = type
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString() = if (type == null) "." else "$type"
|
||||
}
|
||||
|
||||
private class MergedSpilledVariableFieldTypeValue(
|
||||
val values: Set<SpilledVariableFieldTypeValue>
|
||||
) : SpilledVariableFieldTypeValue(null, null) {
|
||||
override var type: Type?
|
||||
get() = values.first().type
|
||||
set(newType) {
|
||||
for (value in values) {
|
||||
value.type = newType
|
||||
}
|
||||
override fun copyOperation(insn: AbstractInsnNode, value: BasicValue?): BasicValue? =
|
||||
when {
|
||||
insn.opcode == Opcodes.ILOAD -> IloadedValue(setOf(insn as VarInsnNode))
|
||||
value == null -> null
|
||||
else -> BasicValue(value.type)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean = other is MergedSpilledVariableFieldTypeValue && other.values == values
|
||||
|
||||
override fun hashCode(): Int = values.hashCode()
|
||||
|
||||
override fun toString(): String = "M$values"
|
||||
}
|
||||
|
||||
// $i$a and $i$f variables should be ignored in merge operation, since they are not used, but set only
|
||||
private class FakeInlinerVariableValue(insn: AbstractInsnNode) : SpilledVariableFieldTypeValue(Type.INT_TYPE, insn) {
|
||||
override fun toString(): String = "@"
|
||||
}
|
||||
|
||||
private operator fun SpilledVariableFieldTypeValue?.plus(other: SpilledVariableFieldTypeValue?): SpilledVariableFieldTypeValue? = when {
|
||||
this == null -> other
|
||||
other == null -> this
|
||||
this == other -> this
|
||||
this is MergedSpilledVariableFieldTypeValue -> {
|
||||
if (other is MergedSpilledVariableFieldTypeValue) MergedSpilledVariableFieldTypeValue(values + other.values)
|
||||
else MergedSpilledVariableFieldTypeValue(values + other)
|
||||
override fun binaryOperation(insn: AbstractInsnNode, v: BasicValue, w: BasicValue): BasicValue? {
|
||||
if (insn.opcode == Opcodes.PUTFIELD) {
|
||||
val expectedType = Type.getType((insn as FieldInsnNode).desc)
|
||||
if (w is IloadedValue && expectedType.isIntLike()) {
|
||||
coerce(w, expectedType)
|
||||
}
|
||||
}
|
||||
return super.binaryOperation(insn, v, w)
|
||||
}
|
||||
other is MergedSpilledVariableFieldTypeValue -> MergedSpilledVariableFieldTypeValue(other.values + this)
|
||||
else -> MergedSpilledVariableFieldTypeValue(setOf(this, other))
|
||||
}
|
||||
|
||||
internal val NULL_TYPE = Type.getObjectType("null")
|
||||
override fun unaryOperation(insn: AbstractInsnNode, value: BasicValue?): BasicValue? {
|
||||
if (insn.opcode == Opcodes.PUTSTATIC) {
|
||||
val expectedType = Type.getType((insn as FieldInsnNode).desc)
|
||||
if (value is IloadedValue && expectedType.isIntLike()) {
|
||||
coerce(value, expectedType)
|
||||
}
|
||||
}
|
||||
return super.unaryOperation(insn, value)
|
||||
}
|
||||
|
||||
// Same as BasicInterpreter, but updates types based on usages
|
||||
private class SpilledVariableFieldTypesInterpreter(
|
||||
private val methodNode: MethodNode
|
||||
) : Interpreter<SpilledVariableFieldTypeValue>(API_VERSION) {
|
||||
override fun newValue(type: Type?): SpilledVariableFieldTypeValue? =
|
||||
if (type == Type.VOID_TYPE) null else SpilledVariableFieldTypeValue(type, null)
|
||||
|
||||
// INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE,
|
||||
// MULTIANEWARRAY and INVOKEDYNAMIC
|
||||
override fun naryOperation(
|
||||
insn: AbstractInsnNode,
|
||||
values: MutableList<out SpilledVariableFieldTypeValue?>
|
||||
): SpilledVariableFieldTypeValue? {
|
||||
fun updateTypes(argTypes: Array<Type>, withReceiver: Boolean) {
|
||||
override fun naryOperation(insn: AbstractInsnNode, values: MutableList<out BasicValue?>): BasicValue? {
|
||||
fun checkTypes(argTypes: Array<Type>, withReceiver: Boolean) {
|
||||
val offset = if (withReceiver) 1 else 0
|
||||
for ((index, argType) in argTypes.withIndex()) {
|
||||
val value = values[index + offset] ?: continue
|
||||
if (argType.isIntType()) {
|
||||
value.type = argType
|
||||
} else if (
|
||||
(value.type == AsmTypes.OBJECT_TYPE && argType != AsmTypes.OBJECT_TYPE) ||
|
||||
value.type == NULL_TYPE || value.type == null
|
||||
) {
|
||||
value.type = argType
|
||||
if (argType.isIntLike() && value is IloadedValue) {
|
||||
coerce(value, argType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SpilledVariableFieldTypeValue(
|
||||
when (insn.opcode) {
|
||||
MULTIANEWARRAY -> {
|
||||
Type.getType((insn as MultiANewArrayInsnNode).desc)
|
||||
}
|
||||
INVOKEDYNAMIC -> {
|
||||
updateTypes(Type.getArgumentTypes((insn as InvokeDynamicInsnNode).desc), false)
|
||||
Type.getReturnType(insn.desc)
|
||||
}
|
||||
INVOKESTATIC -> {
|
||||
updateTypes(Type.getArgumentTypes((insn as MethodInsnNode).desc), false)
|
||||
Type.getReturnType(insn.desc)
|
||||
}
|
||||
INVOKEVIRTUAL, INVOKEINTERFACE, INVOKESPECIAL -> {
|
||||
updateTypes(Type.getArgumentTypes((insn as MethodInsnNode).desc), true)
|
||||
Type.getReturnType(insn.desc)
|
||||
}
|
||||
else -> {
|
||||
unreachable(insn)
|
||||
}
|
||||
}, insn
|
||||
)
|
||||
}
|
||||
|
||||
private fun Type.isIntType(): Boolean = when (sort) {
|
||||
Type.BOOLEAN, Type.BYTE, Type.CHAR, Type.SHORT, Type.INT -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun unreachable(insn: AbstractInsnNode): Nothing = error("Unreachable instruction ${insn.insnOpcodeText}")
|
||||
|
||||
// IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE
|
||||
override fun ternaryOperation(
|
||||
insn: AbstractInsnNode,
|
||||
arrayref: SpilledVariableFieldTypeValue?,
|
||||
index: SpilledVariableFieldTypeValue?,
|
||||
value: SpilledVariableFieldTypeValue?
|
||||
): SpilledVariableFieldTypeValue? {
|
||||
when (insn.opcode) {
|
||||
IASTORE, LASTORE, FASTORE, DASTORE, AASTORE -> {
|
||||
// nothing to do
|
||||
Opcodes.INVOKEDYNAMIC -> {
|
||||
checkTypes(Type.getArgumentTypes((insn as InvokeDynamicInsnNode).desc), false)
|
||||
}
|
||||
BASTORE -> {
|
||||
value?.type = if (arrayref?.type?.descriptor == "[Z") Type.BOOLEAN_TYPE else Type.BYTE_TYPE
|
||||
Opcodes.INVOKESTATIC -> {
|
||||
checkTypes(Type.getArgumentTypes((insn as MethodInsnNode).desc), false)
|
||||
}
|
||||
CASTORE -> {
|
||||
value?.type = Type.CHAR_TYPE
|
||||
Opcodes.INVOKEVIRTUAL, Opcodes.INVOKEINTERFACE, Opcodes.INVOKESPECIAL -> {
|
||||
checkTypes(Type.getArgumentTypes((insn as MethodInsnNode).desc), true)
|
||||
}
|
||||
SASTORE -> {
|
||||
value?.type = Type.SHORT_TYPE
|
||||
}
|
||||
else -> unreachable(insn)
|
||||
}
|
||||
return null
|
||||
return super.naryOperation(insn, values)
|
||||
}
|
||||
|
||||
override fun merge(v: SpilledVariableFieldTypeValue?, w: SpilledVariableFieldTypeValue?): SpilledVariableFieldTypeValue? =
|
||||
override fun ternaryOperation(insn: AbstractInsnNode, arrayref: BasicValue?, index: BasicValue?, value: BasicValue?): BasicValue? {
|
||||
when (insn.opcode) {
|
||||
Opcodes.BASTORE -> {
|
||||
if (value is IloadedValue) {
|
||||
val type = if (arrayref?.type?.descriptor == "[Z") Type.BOOLEAN_TYPE else Type.BYTE_TYPE
|
||||
coerce(value, type)
|
||||
}
|
||||
}
|
||||
Opcodes.CASTORE -> {
|
||||
if (value is IloadedValue) {
|
||||
coerce(value, Type.CHAR_TYPE)
|
||||
}
|
||||
}
|
||||
Opcodes.SASTORE -> {
|
||||
if (value is IloadedValue) {
|
||||
coerce(value, Type.SHORT_TYPE)
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.ternaryOperation(insn, arrayref, index, value)
|
||||
}
|
||||
|
||||
override fun merge(v: BasicValue, w: BasicValue): BasicValue =
|
||||
when {
|
||||
v is FakeInlinerVariableValue -> w
|
||||
w is FakeInlinerVariableValue -> v
|
||||
v?.type?.isIntType() == true && w?.type?.isIntType() == true -> v + w
|
||||
v != null && v.type == null -> w
|
||||
w != null && w.type == null -> v
|
||||
v?.type == w?.type -> v
|
||||
v?.type?.sort == Type.OBJECT && w?.type?.sort == Type.OBJECT -> {
|
||||
when {
|
||||
v.type == AsmTypes.OBJECT_TYPE -> v
|
||||
w.type == AsmTypes.OBJECT_TYPE -> w
|
||||
else -> SpilledVariableFieldTypeValue(AsmTypes.OBJECT_TYPE, v.insn)
|
||||
v is IloadedValue && w is IloadedValue && v.type == w.type -> {
|
||||
val insns = v.insns + w.insns
|
||||
insns.find { it in needsToBeCoerced }?.let {
|
||||
val type = needsToBeCoerced[it]!!
|
||||
coerce(v, type)
|
||||
coerce(w, type)
|
||||
}
|
||||
IloadedValue(insns)
|
||||
}
|
||||
else -> SpilledVariableFieldTypeValue(null, v?.insn ?: w?.insn)
|
||||
v.type == w.type -> {
|
||||
if (w is IloadedValue) w else v
|
||||
}
|
||||
else -> super.merge(v, w)
|
||||
}
|
||||
|
||||
// IRETURN, LRETURN, FRETURN, DRETURN, ARETURN
|
||||
override fun returnOperation(insn: AbstractInsnNode, value: SpilledVariableFieldTypeValue?, expected: SpilledVariableFieldTypeValue?) {
|
||||
if (insn.opcode == IRETURN) {
|
||||
value?.type = expected?.type
|
||||
}
|
||||
}
|
||||
|
||||
// INEG, LNEG, FNEG, DNEG, IINC, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L,
|
||||
// F2D, D2I, D2L, D2F, I2B, I2C, I2S, IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE,
|
||||
// TABLESWITCH, LOOKUPSWITCH, IRETURN, LRETURN, FRETURN, DRETURN, ARETURN,
|
||||
// PUTSTATIC, GETFIELD, NEWARRAY, ANEWARRAY, ARRAYLENGTH, ATHROW, CHECKCAST,
|
||||
// INSTANCEOF, MONITORENTER, MONITOREXIT, IFNULL, IFNONNULL
|
||||
override fun unaryOperation(insn: AbstractInsnNode, value: SpilledVariableFieldTypeValue?): SpilledVariableFieldTypeValue? =
|
||||
when (insn.opcode) {
|
||||
INEG, LNEG, FNEG, DNEG, IINC -> SpilledVariableFieldTypeValue(value?.type, insn)
|
||||
I2L, F2L, D2L -> SpilledVariableFieldTypeValue(Type.LONG_TYPE, insn)
|
||||
I2F, L2F, D2F -> SpilledVariableFieldTypeValue(Type.FLOAT_TYPE, insn)
|
||||
L2D, I2D, F2D -> SpilledVariableFieldTypeValue(Type.DOUBLE_TYPE, insn)
|
||||
L2I, F2I, D2I, ARRAYLENGTH -> SpilledVariableFieldTypeValue(Type.INT_TYPE, insn)
|
||||
I2B -> SpilledVariableFieldTypeValue(Type.BYTE_TYPE, insn)
|
||||
I2C -> SpilledVariableFieldTypeValue(Type.CHAR_TYPE, insn)
|
||||
I2S -> SpilledVariableFieldTypeValue(Type.SHORT_TYPE, insn)
|
||||
IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, TABLESWITCH, LOOKUPSWITCH, IRETURN, LRETURN, FRETURN, DRETURN, ARETURN,
|
||||
ATHROW, MONITORENTER, MONITOREXIT, IFNULL, IFNONNULL -> null
|
||||
PUTSTATIC -> {
|
||||
val expectedType = Type.getType((insn as FieldInsnNode).desc)
|
||||
if (expectedType.isIntType()) {
|
||||
value?.type = expectedType
|
||||
}
|
||||
null
|
||||
}
|
||||
GETFIELD -> SpilledVariableFieldTypeValue(Type.getType((insn as FieldInsnNode).desc), insn)
|
||||
NEWARRAY -> when ((insn as IntInsnNode).operand) {
|
||||
T_BOOLEAN -> SpilledVariableFieldTypeValue(Type.getType("[Z"), insn)
|
||||
T_CHAR -> SpilledVariableFieldTypeValue(Type.getType("[C"), insn)
|
||||
T_BYTE -> SpilledVariableFieldTypeValue(Type.getType("[B"), insn)
|
||||
T_SHORT -> SpilledVariableFieldTypeValue(Type.getType("[S"), insn)
|
||||
T_INT -> SpilledVariableFieldTypeValue(Type.getType("[I"), insn)
|
||||
T_FLOAT -> SpilledVariableFieldTypeValue(Type.getType("[F"), insn)
|
||||
T_DOUBLE -> SpilledVariableFieldTypeValue(Type.getType("[D"), insn)
|
||||
T_LONG -> SpilledVariableFieldTypeValue(Type.getType("[J"), insn)
|
||||
else -> unreachable(insn)
|
||||
}
|
||||
ANEWARRAY -> SpilledVariableFieldTypeValue(Type.getType("[${Type.getObjectType((insn as TypeInsnNode).desc)}"), insn)
|
||||
CHECKCAST -> SpilledVariableFieldTypeValue(Type.getObjectType((insn as TypeInsnNode).desc), insn)
|
||||
INSTANCEOF -> SpilledVariableFieldTypeValue(Type.BOOLEAN_TYPE, insn)
|
||||
else -> unreachable(insn)
|
||||
}
|
||||
|
||||
// IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IADD,
|
||||
// LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV,
|
||||
// LDIV, FDIV, DDIV, IREM, LREM, FREM, DREM, ISHL, LSHL, ISHR, LSHR, IUSHR,
|
||||
// LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, LCMP, FCMPL, FCMPG, DCMPL,
|
||||
// DCMPG, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE,
|
||||
// IF_ACMPEQ, IF_ACMPNE, PUTFIELD
|
||||
override fun binaryOperation(
|
||||
insn: AbstractInsnNode,
|
||||
v: SpilledVariableFieldTypeValue?,
|
||||
w: SpilledVariableFieldTypeValue?
|
||||
): SpilledVariableFieldTypeValue? = when (insn.opcode) {
|
||||
IALOAD, IADD, ISUB, IMUL, IDIV, IREM, ISHL, ISHR, IUSHR, IAND, IOR, IXOR, LCMP, FCMPL, FCMPG, DCMPL,
|
||||
DCMPG -> SpilledVariableFieldTypeValue(Type.INT_TYPE, insn)
|
||||
LALOAD, LADD, LSUB, LMUL, LDIV, LREM, LSHL, LSHR, LUSHR, LAND, LOR, LXOR -> SpilledVariableFieldTypeValue(Type.LONG_TYPE, insn)
|
||||
FALOAD, FADD, FSUB, FMUL, FDIV, FREM -> SpilledVariableFieldTypeValue(Type.FLOAT_TYPE, insn)
|
||||
DALOAD, DADD, DSUB, DMUL, DDIV, DREM -> SpilledVariableFieldTypeValue(Type.DOUBLE_TYPE, insn)
|
||||
AALOAD -> SpilledVariableFieldTypeValue(AsmTypes.OBJECT_TYPE, insn)
|
||||
BALOAD -> SpilledVariableFieldTypeValue(if (v?.type?.descriptor == "[Z") Type.BOOLEAN_TYPE else Type.BYTE_TYPE, insn)
|
||||
CALOAD -> SpilledVariableFieldTypeValue(Type.CHAR_TYPE, insn)
|
||||
SALOAD -> SpilledVariableFieldTypeValue(Type.SHORT_TYPE, insn)
|
||||
IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> null
|
||||
PUTFIELD -> {
|
||||
val expectedType = Type.getType((insn as FieldInsnNode).desc)
|
||||
if (expectedType.isIntType()) {
|
||||
w?.type = expectedType
|
||||
}
|
||||
null
|
||||
}
|
||||
else -> unreachable(insn)
|
||||
}
|
||||
|
||||
// ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE,
|
||||
// ASTORE, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP
|
||||
override fun copyOperation(insn: AbstractInsnNode, value: SpilledVariableFieldTypeValue?): SpilledVariableFieldTypeValue? {
|
||||
return when (insn.opcode) {
|
||||
// If same ICONST is stored into several slots, thay can have different types
|
||||
// For example,
|
||||
// val b: Byte = 1
|
||||
// val i: Int = b.toInt()
|
||||
// In this case, `b` and `i` have the same source, but different types.
|
||||
// The example also shows, that the types should be `I`.
|
||||
ISTORE -> {
|
||||
if (value is FakeInlinerVariableValue && insn.previous.opcode == ICONST_0) return value
|
||||
findLvtRecord((insn as VarInsnNode).`var`, insn)?.let {
|
||||
if (Type.getType(it.desc) == value?.type) return value
|
||||
}
|
||||
var current: AbstractInsnNode? = insn
|
||||
while (current != null && current !is LabelNode) {
|
||||
current = current.next
|
||||
}
|
||||
while (current is LabelNode) {
|
||||
findLvtRecord(insn.`var`, current)?.let {
|
||||
if (Type.getType(it.desc) == value?.type) return value
|
||||
}
|
||||
current = current.next
|
||||
}
|
||||
if (value?.type == Type.INT_TYPE) return value
|
||||
SpilledVariableFieldTypeValue(Type.INT_TYPE, insn)
|
||||
}
|
||||
// Sometimes we cannot get the type from the usage only
|
||||
// For example,
|
||||
// val c = '1'
|
||||
// if (c == '2) ...
|
||||
// In this case, update the type using information from LVT
|
||||
ILOAD -> {
|
||||
if (value is FakeInlinerVariableValue) return SpilledVariableFieldTypeValue(value.type, value.insn)
|
||||
findLvtRecord((insn as VarInsnNode).`var`, insn)?.let { value?.type = Type.getType(it.desc) }
|
||||
value
|
||||
}
|
||||
else -> value
|
||||
}
|
||||
}
|
||||
|
||||
private fun findLvtRecord(index: Int, insn: AbstractInsnNode): LocalVariableNode? = methodNode.localVariables.find { local ->
|
||||
local.index == index &&
|
||||
methodNode.instructions.indexOf(local.start) <= methodNode.instructions.indexOf(insn) &&
|
||||
methodNode.instructions.indexOf(insn) < methodNode.instructions.indexOf(local.end)
|
||||
}
|
||||
|
||||
// ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4,
|
||||
// ICONST_5, LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0,
|
||||
// DCONST_1, BIPUSH, SIPUSH, LDC, JSR, GETSTATIC, NEW
|
||||
override fun newOperation(insn: AbstractInsnNode): SpilledVariableFieldTypeValue? {
|
||||
return when (insn.opcode) {
|
||||
ICONST_0 -> {
|
||||
if (insn.next?.opcode == ISTORE) {
|
||||
// Find out, whether this is fake inliner variable
|
||||
// Unlike old JVM BE, JVM_IR does not generate them in a specific pattern,
|
||||
// that we can just recognize.
|
||||
// So, run instructions until LabelNode and check whether this is, in fact, fake inliner variable
|
||||
if (findLvtRecord((insn.next as VarInsnNode).`var`, insn.next) != null) {
|
||||
return SpilledVariableFieldTypeValue(Type.INT_TYPE, insn)
|
||||
}
|
||||
var current: AbstractInsnNode? = insn.next?.next
|
||||
while (current != null && current !is LabelNode) {
|
||||
current = current.next
|
||||
}
|
||||
while (current is LabelNode) {
|
||||
val lvtRecord = findLvtRecord((insn.next as VarInsnNode).`var`, current)
|
||||
// fake variables do not have LVT entry is they are inside `run` or `apply`
|
||||
if (lvtRecord == null || isFakeLocalVariableForInline(lvtRecord.name)) {
|
||||
return FakeInlinerVariableValue(insn)
|
||||
}
|
||||
current = current.next
|
||||
}
|
||||
}
|
||||
SpilledVariableFieldTypeValue(Type.INT_TYPE, insn)
|
||||
}
|
||||
ACONST_NULL -> SpilledVariableFieldTypeValue(NULL_TYPE, insn)
|
||||
ICONST_M1, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5 -> SpilledVariableFieldTypeValue(Type.INT_TYPE, insn)
|
||||
LCONST_0, LCONST_1 -> SpilledVariableFieldTypeValue(Type.LONG_TYPE, insn)
|
||||
FCONST_0, FCONST_1, FCONST_2 -> SpilledVariableFieldTypeValue(Type.FLOAT_TYPE, insn)
|
||||
DCONST_0, DCONST_1 -> SpilledVariableFieldTypeValue(Type.DOUBLE_TYPE, insn)
|
||||
BIPUSH -> SpilledVariableFieldTypeValue(Type.BYTE_TYPE, insn)
|
||||
SIPUSH -> SpilledVariableFieldTypeValue(Type.SHORT_TYPE, insn)
|
||||
LDC -> when ((insn as LdcInsnNode).cst) {
|
||||
is Int -> SpilledVariableFieldTypeValue(Type.INT_TYPE, insn)
|
||||
is Long -> SpilledVariableFieldTypeValue(Type.LONG_TYPE, insn)
|
||||
is Float -> SpilledVariableFieldTypeValue(Type.FLOAT_TYPE, insn)
|
||||
is Double -> SpilledVariableFieldTypeValue(Type.DOUBLE_TYPE, insn)
|
||||
is String -> SpilledVariableFieldTypeValue(AsmTypes.JAVA_STRING_TYPE, insn)
|
||||
is Type -> SpilledVariableFieldTypeValue(AsmTypes.JAVA_CLASS_TYPE, insn)
|
||||
else -> SpilledVariableFieldTypeValue(AsmTypes.OBJECT_TYPE, insn)
|
||||
}
|
||||
JSR -> SpilledVariableFieldTypeValue(Type.VOID_TYPE, insn)
|
||||
GETSTATIC -> SpilledVariableFieldTypeValue(Type.getType((insn as FieldInsnNode).desc), insn)
|
||||
NEW -> SpilledVariableFieldTypeValue(Type.getObjectType((insn as TypeInsnNode).desc), insn)
|
||||
else -> unreachable(insn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun performSpilledVariableFieldTypesAnalysis(
|
||||
methodNode: MethodNode,
|
||||
thisName: String
|
||||
): Array<out Frame<SpilledVariableFieldTypeValue>?> =
|
||||
MethodAnalyzer(thisName, methodNode, SpilledVariableFieldTypesInterpreter(methodNode)).analyze()
|
||||
): Array<out Frame<BasicValue>?> {
|
||||
val interpreter = IntLikeCoerceInterpreter()
|
||||
MethodAnalyzer(thisName, methodNode, interpreter).analyze()
|
||||
for ((insn, type) in interpreter.needsToBeCoerced) {
|
||||
methodNode.instructions.insert(insn, withInstructionAdapter { coerceInt(type, this) })
|
||||
}
|
||||
return MethodAnalyzer(thisName, methodNode, OptimizationBasicInterpreter()).analyze()
|
||||
}
|
||||
|
||||
internal val Value.type: Type?
|
||||
get() = when (this) {
|
||||
is BasicValue -> type
|
||||
is SpilledVariableFieldTypeValue -> type
|
||||
else -> error("Unexpected type of $this")
|
||||
}
|
||||
private fun coerceInt(to: Type, v: InstructionAdapter) {
|
||||
if (to == Type.BOOLEAN_TYPE) {
|
||||
with(v) {
|
||||
val zeroLabel = Label()
|
||||
val resLabel = Label()
|
||||
ifeq(zeroLabel)
|
||||
iconst(1)
|
||||
goTo(resLabel)
|
||||
mark(zeroLabel)
|
||||
iconst(0)
|
||||
mark(resLabel)
|
||||
}
|
||||
} else {
|
||||
StackValue.coerce(Type.INT_TYPE, to, v)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Type.isIntLike(): Boolean = when (sort) {
|
||||
Type.BOOLEAN, Type.BYTE, Type.CHAR, Type.SHORT -> true
|
||||
else -> false
|
||||
}
|
||||
+2
-2
@@ -37,5 +37,5 @@ fun box(): String {
|
||||
return "OK"
|
||||
}
|
||||
|
||||
// 1 PUTFIELD .*\.Z\$0 : Z
|
||||
// 1 PUTFIELD .*\.Z\$1 : Z
|
||||
// 1 PUTFIELD .*\.I\$0 : I
|
||||
// 1 PUTFIELD .*\.I\$1 : I
|
||||
|
||||
+1
-1
@@ -36,4 +36,4 @@ fun box(): String {
|
||||
return "OK"
|
||||
}
|
||||
|
||||
// 1 PUTFIELD .*\.B\$0 : B
|
||||
// 1 PUTFIELD .*\.I\$0 : I
|
||||
|
||||
Vendored
+1
-1
@@ -35,4 +35,4 @@ fun box(): String {
|
||||
return "OK"
|
||||
}
|
||||
|
||||
// 1 PUTFIELD .*\.Z\$0 : Z
|
||||
// 1 PUTFIELD .*\.I\$0 : I
|
||||
|
||||
+1
-1
@@ -35,4 +35,4 @@ fun box(): String {
|
||||
return "OK"
|
||||
}
|
||||
|
||||
// 1 PUTFIELD .*\.B\$0 : B
|
||||
// 1 PUTFIELD .*\.I\$0 : I
|
||||
|
||||
+1
-1
@@ -35,4 +35,4 @@ fun box(): String {
|
||||
return "OK"
|
||||
}
|
||||
|
||||
// 1 PUTFIELD .*\.Z\$0 : Z
|
||||
// 1 PUTFIELD .*\.I\$0 : I
|
||||
|
||||
Vendored
+1
-1
@@ -38,5 +38,5 @@ fun box(): String {
|
||||
return "OK"
|
||||
}
|
||||
|
||||
// 1 PUTFIELD .*\.B\$0 : B
|
||||
// 1 PUTFIELD .*\.I\$0 : I
|
||||
// 1 PUTFIELD .*\.I\$1 : I
|
||||
|
||||
+1
-5
@@ -81,8 +81,4 @@ fun box(): String {
|
||||
return "OK"
|
||||
}
|
||||
|
||||
// 1 PUTFIELD .*\.B\$0 : B
|
||||
// 1 PUTFIELD .*\.C\$0 : C
|
||||
// 1 PUTFIELD .*\.S\$0 : S
|
||||
// 1 PUTFIELD .*\.Z\$0 : Z
|
||||
// 1 PUTFIELD .*\.I\$0 : I
|
||||
// 5 PUTFIELD .*\.I\$0 : I
|
||||
|
||||
+1
-5
@@ -85,8 +85,4 @@ fun box(): String {
|
||||
return "OK"
|
||||
}
|
||||
|
||||
// 1 PUTFIELD .*\.B\$0 : B
|
||||
// 1 PUTFIELD .*\.C\$0 : C
|
||||
// 1 PUTFIELD .*\.S\$0 : S
|
||||
// 1 PUTFIELD .*\.Z\$0 : Z
|
||||
// 1 PUTFIELD .*\.I\$0 : I
|
||||
// 5 PUTFIELD .*\.I\$0 : I
|
||||
|
||||
+1
-5
@@ -71,8 +71,4 @@ fun box(): String {
|
||||
return "OK"
|
||||
}
|
||||
|
||||
// 1 PUTFIELD .*\.B\$0 : B
|
||||
// 1 PUTFIELD .*\.C\$0 : C
|
||||
// 1 PUTFIELD .*\.S\$0 : S
|
||||
// 1 PUTFIELD .*\.Z\$0 : Z
|
||||
// 1 PUTFIELD .*\.I\$0 : I
|
||||
// 5 PUTFIELD .*\.I\$0 : I
|
||||
|
||||
+1
-5
@@ -61,8 +61,4 @@ fun box(): String {
|
||||
return "OK"
|
||||
}
|
||||
|
||||
// 1 PUTFIELD .*\.B\$0 : B
|
||||
// 1 PUTFIELD .*\.C\$0 : C
|
||||
// 1 PUTFIELD .*\.S\$0 : S
|
||||
// 1 PUTFIELD .*\.Z\$0 : Z
|
||||
// 1 PUTFIELD .*\.I\$0 : I
|
||||
// 5 PUTFIELD .*\.I\$0 : I
|
||||
|
||||
Reference in New Issue
Block a user