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:
Ilmir Usmanov
2020-06-30 14:03:51 +02:00
parent e7f33ac051
commit 697c8637ee
12 changed files with 122 additions and 347 deletions
@@ -647,7 +647,7 @@ class CoroutineTransformerMethodVisitor(
} }
for ((index, basicValue) in variablesToSpill) { for ((index, basicValue) in variablesToSpill) {
if (basicValue.type == NULL_TYPE) { if (basicValue == StrictBasicValue.NULL_VALUE) {
postponedActions.add { postponedActions.add {
with(instructions) { with(instructions) {
insert(suspension.tryCatchBlockEndLabelAfterSuspensionCall, withInstructionAdapter { insert(suspension.tryCatchBlockEndLabelAfterSuspensionCall, withInstructionAdapter {
@@ -5,365 +5,156 @@
package org.jetbrains.kotlin.codegen.coroutines package org.jetbrains.kotlin.codegen.coroutines
import org.jetbrains.kotlin.codegen.inline.insnOpcodeText import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline
import org.jetbrains.kotlin.codegen.optimization.common.MethodAnalyzer import org.jetbrains.kotlin.codegen.optimization.common.MethodAnalyzer
import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
import org.jetbrains.org.objectweb.asm.Opcodes.* 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.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.tree.* import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue 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.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', // BasicValue interpreter from ASM does not distinct 'int' types from other int-like types like 'byte' or 'boolean',
// neither do HotSpot and JVM spec. // 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, // 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. // 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) // 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 class IloadedValue(val insns: Set<VarInsnNode>) : BasicValue(Type.INT_TYPE)
internal open class SpilledVariableFieldTypeValue(open var type: Type?, val insn: AbstractInsnNode?) : Value {
override fun getSize(): Int = type?.size ?: 1
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" override fun copyOperation(insn: AbstractInsnNode, value: BasicValue?): BasicValue? =
} when {
insn.opcode == Opcodes.ILOAD -> IloadedValue(setOf(insn as VarInsnNode))
private class MergedSpilledVariableFieldTypeValue( value == null -> null
val values: Set<SpilledVariableFieldTypeValue> else -> BasicValue(value.type)
) : SpilledVariableFieldTypeValue(null, null) {
override var type: Type?
get() = values.first().type
set(newType) {
for (value in values) {
value.type = newType
}
} }
override fun equals(other: Any?): Boolean = other is MergedSpilledVariableFieldTypeValue && other.values == values override fun binaryOperation(insn: AbstractInsnNode, v: BasicValue, w: BasicValue): BasicValue? {
if (insn.opcode == Opcodes.PUTFIELD) {
override fun hashCode(): Int = values.hashCode() val expectedType = Type.getType((insn as FieldInsnNode).desc)
if (w is IloadedValue && expectedType.isIntLike()) {
override fun toString(): String = "M$values" coerce(w, expectedType)
} }
}
// $i$a and $i$f variables should be ignored in merge operation, since they are not used, but set only return super.binaryOperation(insn, v, w)
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)
} }
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 override fun naryOperation(insn: AbstractInsnNode, values: MutableList<out BasicValue?>): BasicValue? {
private class SpilledVariableFieldTypesInterpreter( fun checkTypes(argTypes: Array<Type>, withReceiver: Boolean) {
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) {
val offset = if (withReceiver) 1 else 0 val offset = if (withReceiver) 1 else 0
for ((index, argType) in argTypes.withIndex()) { for ((index, argType) in argTypes.withIndex()) {
val value = values[index + offset] ?: continue val value = values[index + offset] ?: continue
if (argType.isIntType()) { if (argType.isIntLike() && value is IloadedValue) {
value.type = argType coerce(value, argType)
} else if (
(value.type == AsmTypes.OBJECT_TYPE && argType != AsmTypes.OBJECT_TYPE) ||
value.type == NULL_TYPE || value.type == null
) {
value.type = 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) { when (insn.opcode) {
IASTORE, LASTORE, FASTORE, DASTORE, AASTORE -> { Opcodes.INVOKEDYNAMIC -> {
// nothing to do checkTypes(Type.getArgumentTypes((insn as InvokeDynamicInsnNode).desc), false)
} }
BASTORE -> { Opcodes.INVOKESTATIC -> {
value?.type = if (arrayref?.type?.descriptor == "[Z") Type.BOOLEAN_TYPE else Type.BYTE_TYPE checkTypes(Type.getArgumentTypes((insn as MethodInsnNode).desc), false)
} }
CASTORE -> { Opcodes.INVOKEVIRTUAL, Opcodes.INVOKEINTERFACE, Opcodes.INVOKESPECIAL -> {
value?.type = Type.CHAR_TYPE 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 { when {
v is FakeInlinerVariableValue -> w v is IloadedValue && w is IloadedValue && v.type == w.type -> {
w is FakeInlinerVariableValue -> v val insns = v.insns + w.insns
v?.type?.isIntType() == true && w?.type?.isIntType() == true -> v + w insns.find { it in needsToBeCoerced }?.let {
v != null && v.type == null -> w val type = needsToBeCoerced[it]!!
w != null && w.type == null -> v coerce(v, type)
v?.type == w?.type -> v coerce(w, type)
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)
} }
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( internal fun performSpilledVariableFieldTypesAnalysis(
methodNode: MethodNode, methodNode: MethodNode,
thisName: String thisName: String
): Array<out Frame<SpilledVariableFieldTypeValue>?> = ): Array<out Frame<BasicValue>?> {
MethodAnalyzer(thisName, methodNode, SpilledVariableFieldTypesInterpreter(methodNode)).analyze() val interpreter = IntLikeCoerceInterpreter()
MethodAnalyzer(thisName, methodNode, interpreter).analyze()
internal val Value.type: Type? for ((insn, type) in interpreter.needsToBeCoerced) {
get() = when (this) { methodNode.instructions.insert(insn, withInstructionAdapter { coerceInt(type, this) })
is BasicValue -> type
is SpilledVariableFieldTypeValue -> type
else -> error("Unexpected type of $this")
} }
return MethodAnalyzer(thisName, methodNode, OptimizationBasicInterpreter()).analyze()
}
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
}
@@ -37,5 +37,5 @@ fun box(): String {
return "OK" return "OK"
} }
// 1 PUTFIELD .*\.Z\$0 : Z // 1 PUTFIELD .*\.I\$0 : I
// 1 PUTFIELD .*\.Z\$1 : Z // 1 PUTFIELD .*\.I\$1 : I
@@ -36,4 +36,4 @@ fun box(): String {
return "OK" return "OK"
} }
// 1 PUTFIELD .*\.B\$0 : B // 1 PUTFIELD .*\.I\$0 : I
@@ -35,4 +35,4 @@ fun box(): String {
return "OK" return "OK"
} }
// 1 PUTFIELD .*\.Z\$0 : Z // 1 PUTFIELD .*\.I\$0 : I
@@ -35,4 +35,4 @@ fun box(): String {
return "OK" return "OK"
} }
// 1 PUTFIELD .*\.B\$0 : B // 1 PUTFIELD .*\.I\$0 : I
@@ -35,4 +35,4 @@ fun box(): String {
return "OK" return "OK"
} }
// 1 PUTFIELD .*\.Z\$0 : Z // 1 PUTFIELD .*\.I\$0 : I
@@ -38,5 +38,5 @@ fun box(): String {
return "OK" return "OK"
} }
// 1 PUTFIELD .*\.B\$0 : B
// 1 PUTFIELD .*\.I\$0 : I // 1 PUTFIELD .*\.I\$0 : I
// 1 PUTFIELD .*\.I\$1 : I
@@ -81,8 +81,4 @@ fun box(): String {
return "OK" return "OK"
} }
// 1 PUTFIELD .*\.B\$0 : B // 5 PUTFIELD .*\.I\$0 : I
// 1 PUTFIELD .*\.C\$0 : C
// 1 PUTFIELD .*\.S\$0 : S
// 1 PUTFIELD .*\.Z\$0 : Z
// 1 PUTFIELD .*\.I\$0 : I
@@ -85,8 +85,4 @@ fun box(): String {
return "OK" return "OK"
} }
// 1 PUTFIELD .*\.B\$0 : B // 5 PUTFIELD .*\.I\$0 : I
// 1 PUTFIELD .*\.C\$0 : C
// 1 PUTFIELD .*\.S\$0 : S
// 1 PUTFIELD .*\.Z\$0 : Z
// 1 PUTFIELD .*\.I\$0 : I
@@ -71,8 +71,4 @@ fun box(): String {
return "OK" return "OK"
} }
// 1 PUTFIELD .*\.B\$0 : B // 5 PUTFIELD .*\.I\$0 : I
// 1 PUTFIELD .*\.C\$0 : C
// 1 PUTFIELD .*\.S\$0 : S
// 1 PUTFIELD .*\.Z\$0 : Z
// 1 PUTFIELD .*\.I\$0 : I
@@ -61,8 +61,4 @@ fun box(): String {
return "OK" return "OK"
} }
// 1 PUTFIELD .*\.B\$0 : B // 5 PUTFIELD .*\.I\$0 : I
// 1 PUTFIELD .*\.C\$0 : C
// 1 PUTFIELD .*\.S\$0 : S
// 1 PUTFIELD .*\.Z\$0 : Z
// 1 PUTFIELD .*\.I\$0 : I