diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala | 273 |
1 files changed, 173 insertions, 100 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala index 8d8ea839e6..dd19ad594f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala @@ -5,35 +5,74 @@ package analysis import scala.annotation.switch import scala.tools.asm.Opcodes._ import scala.tools.asm.Type -import scala.tools.asm.tree.{MultiANewArrayInsnNode, InvokeDynamicInsnNode, MethodInsnNode, AbstractInsnNode} +import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis.{Frame, Value} import opt.BytecodeUtils._ -import collection.immutable object InstructionStackEffect { - private var cache: immutable.IntMap[(Int, Int)] = immutable.IntMap.empty - private def t(x: Int, y: Int): (Int, Int) = { - // x can go up to 255 (number of parameters of a method, dimensions in multianewarray) we cache - // x up to 10, which covers most cases and limits the cache. y doesn't go above 6 (see cases). - if (x > 10 || y > 6) (x, y) - else { - val key = (x << 8) + y // this would work for any x < 256 - if (cache contains key) { - cache(key) - } else { - val r = (x, y) - cache += key -> r - r - } - } + val consShift = 3 + val prodMask = (1 << consShift) - 1 + + def cons(i: Int) = i >>> consShift + def prod(i: Int) = i & prodMask + + private def t(x: Int, y: Int): Int = (x << consShift) | y + + /** + * Returns the number of stack values consumed and produced by `insn`, encoded in a single `Int` + * (the `cons` / `prod` extract individual values). The returned values are correct for use in + * asm's Analyzer framework. For example, a LLOAD instruction produces one stack value. See also + * doc in `analysis` package object. + * + * This method requires the `frame` to be in the state **before** executing / interpreting the + * `insn`. + */ + def forAsmAnalysis[V <: Value](insn: AbstractInsnNode, frame: Frame[V]): Int = computeConsProd(insn, forClassfile = false, conservative = false, frame = frame) + + /** + * Returns the maximal possible growth of the stack when executing `insn`. The returned value + * is usually the same as expected by asm's Analyzer framework, but it may be larger. For + * example, consider a POP2 instruction: + * - if two size-1 values are popped, then the asm Analyzer consumes two values + * - if a size-2 value is popped, the asm Analyzer consumes only one stack slot (see doc in the + * `analysis` package object) + * + * If a precise result is needed, invoke the `forAsmAnalysis` and provide a `frame` value that + * allows looking up the sizes of values on the stack. + */ + def maxStackGrowth(insn: AbstractInsnNode): Int = { + val prodCons = computeConsProd(insn, forClassfile = false, conservative = true) + prod(prodCons) - cons(prodCons) } /** - * Returns a pair with the number of stack values consumed and produced by `insn`. - * This method requires the `frame` to be in the state **before** executing / interpreting - * the `insn`. + * Returns the number of stack values consumed and produced by `insn`, encoded in a single `Int` + * (the `cons` / `prod` extract individual values). The returned values are correct for writing + * into a classfile (see doc on the `analysis` package object). */ - def apply[V <: Value](insn: AbstractInsnNode, frame: Frame[V]): (Int, Int) = { + def forClassfile(insn: AbstractInsnNode): Int = computeConsProd(insn, forClassfile = true, conservative = false) + + private def invokeConsProd(methodDesc: String, insn: AbstractInsnNode, forClassfile: Boolean): Int = { + val consumesReceiver = insn.getOpcode != INVOKESTATIC && insn.getOpcode != INVOKEDYNAMIC + if (forClassfile) { + val sizes = Type.getArgumentsAndReturnSizes(methodDesc) + val cons = (sizes >> 2) - (if (consumesReceiver) 0 else 1) + val prod = sizes & 0x03 + t(cons, prod) + } else { + val cons = Type.getArgumentTypes(methodDesc).length + (if (consumesReceiver) 1 else 0) + val prod = if (Type.getReturnType(methodDesc) == Type.VOID_TYPE) 0 else 1 + t(cons, prod) + } + } + + private def fieldInsnIsLongOrDouble(insn: AbstractInsnNode) = { + val d = insn.asInstanceOf[FieldInsnNode].desc + d == "J" || d == "D" + } + + private def computeConsProd[V <: Value](insn: AbstractInsnNode, forClassfile: Boolean, conservative: Boolean, frame: Frame[V] = null): Int = { + // not used if `forClassfile || conservative`: in these cases, `frame` is allowed to be `null` def peekStack(n: Int): V = frame.peekStack(n) (insn.getOpcode: @switch) match { @@ -48,142 +87,176 @@ object InstructionStackEffect { ICONST_3 | ICONST_4 | ICONST_5 | - LCONST_0 | - LCONST_1 | FCONST_0 | FCONST_1 | FCONST_2 | - DCONST_0 | - DCONST_1 | BIPUSH | SIPUSH | - LDC | ILOAD | - LLOAD | FLOAD | - DLOAD | ALOAD => t(0, 1) + case LDC => + if (forClassfile) insn.asInstanceOf[LdcInsnNode].cst match { + case _: java.lang.Long | _: java.lang.Double => t(0, 2) + case _ => t(0, 1) + } else + t(0, 1) + + case LCONST_0 | + LCONST_1 | + DCONST_0 | + DCONST_1 | + LLOAD | + DLOAD => if (forClassfile) t(0, 2) else t(0, 1) + case IALOAD | - LALOAD | FALOAD | - DALOAD | AALOAD | BALOAD | CALOAD | SALOAD => t(2, 1) + case LALOAD | + DALOAD => if (forClassfile) t(2, 2) else t(2, 1) + case ISTORE | - LSTORE | FSTORE | - DSTORE | ASTORE => t(1, 0) + case LSTORE | + DSTORE => if (forClassfile) t(2, 0) else t(1, 0) + case IASTORE | - LASTORE | FASTORE | - DASTORE | AASTORE | BASTORE | CASTORE | SASTORE => t(3, 0) + case LASTORE | + DASTORE => if (forClassfile) t(4, 0) else t(3, 0) + case POP => t(1, 0) case POP2 => - val isSize2 = peekStack(0).getSize == 2 - if (isSize2) t(1, 0) else t(2, 0) + if (forClassfile) t(2, 0) + else if (conservative) t(1, 0) + else { + val isSize2 = peekStack(0).getSize == 2 + if (isSize2) t(1, 0) else t(2, 0) + } case DUP => t(1, 2) case DUP_X1 => t(2, 3) case DUP_X2 => - val isSize2 = peekStack(1).getSize == 2 - if (isSize2) t(2, 3) else t(3, 4) + if (forClassfile || conservative) t(3, 4) + else { + val isSize2 = peekStack(1).getSize == 2 + if (isSize2) t(2, 3) else t(3, 4) + } case DUP2 => - val isSize2 = peekStack(0).getSize == 2 - if (isSize2) t(1, 2) else t(2, 4) + if (forClassfile || conservative) t(2, 4) + else { + val isSize2 = peekStack(0).getSize == 2 + if (isSize2) t(1, 2) else t(2, 4) + } case DUP2_X1 => - val isSize2 = peekStack(0).getSize == 2 - if (isSize2) t(2, 3) else t(3, 4) + if (forClassfile || conservative) t(3, 5) + else { + val isSize2 = peekStack(0).getSize == 2 + if (isSize2) t(2, 3) else t(3, 5) + } case DUP2_X2 => - val v1isSize2 = peekStack(0).getSize == 2 - if (v1isSize2) { - val v2isSize2 = peekStack(1).getSize == 2 - if (v2isSize2) t(2, 3) else t(3, 4) - } else { - val v3isSize2 = peekStack(2).getSize == 2 - if (v3isSize2) t(3, 5) else t(4, 6) + if (forClassfile || conservative) t(4, 6) + else { + val v1isSize2 = peekStack(0).getSize == 2 + if (v1isSize2) { + val v2isSize2 = peekStack(1).getSize == 2 + if (v2isSize2) t(2, 3) else t(3, 4) + } else { + val v3isSize2 = peekStack(2).getSize == 2 + if (v3isSize2) t(3, 5) else t(4, 6) + } } case SWAP => t(2, 2) case IADD | - LADD | FADD | - DADD | ISUB | - LSUB | FSUB | - DSUB | IMUL | - LMUL | FMUL | - DMUL | IDIV | - LDIV | FDIV | - DDIV | IREM | + FREM => t(2, 1) + + case LADD | + DADD | + LSUB | + DSUB | + LMUL | + DMUL | + LDIV | + DDIV | LREM | - FREM | - DREM => t(2, 1) + DREM => if (forClassfile) t(4, 2) else t(2, 1) case INEG | - LNEG | - FNEG | - DNEG => t(1, 1) + FNEG => t(1, 1) + + case LNEG | + DNEG => if (forClassfile) t(2, 2) else t(1, 1) case ISHL | - LSHL | ISHR | - LSHR | IUSHR | - LUSHR | IAND | - LAND | IOR | + IXOR => t(2, 1) + + case LSHL | + LSHR | + LUSHR => if (forClassfile) t(3, 2) else t(2, 1) + + case LAND | LOR | - IXOR | - LXOR => t(2, 1) + LXOR => if (forClassfile) t(4, 2) else t(2, 1) case IINC => t(0, 0) - case I2L | - I2F | - I2D | - L2I | - L2F | - L2D | + case I2F | F2I | - F2L | - F2D | - D2I | - D2L | - D2F | I2B | I2C | I2S => t(1, 1) + case I2L | + I2D | + F2L | + F2D => if (forClassfile) t(1, 2) else t(1, 1) + + case L2I | + L2F | + D2I | + D2F => if (forClassfile) t(2, 1) else t(1, 1) + + case L2D | + D2L => if (forClassfile) t(2, 2) else t(1, 1) + + case FCMPL | + FCMPG => t(2, 1) + case LCMP | - FCMPL | - FCMPG | DCMPL | - DCMPG => t(2, 1) + DCMPG => if (forClassfile) t(4, 1) else t(2, 1) case IFEQ | IFNE | @@ -211,35 +284,36 @@ object InstructionStackEffect { LOOKUPSWITCH => t(1, 0) case IRETURN | - LRETURN | FRETURN | - DRETURN | ARETURN => t(1, 0) // Frame.execute consumes one stack value + case LRETURN | + DRETURN => if (forClassfile) t(2, 0) else t(1, 0) + case RETURN => t(0, 0) // Frame.execute does not change the stack - case GETSTATIC => t(0, 1) + case GETSTATIC => + val prod = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1 + t(0, prod) - case PUTSTATIC => t(1, 0) + case PUTSTATIC => + val cons = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1 + t(cons, 0) - case GETFIELD => t(1, 1) + case GETFIELD => + val prod = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1 + t(1, prod) - case PUTFIELD => t(2, 0) + case PUTFIELD => + val cons = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 3 else 2 + t(cons, 0) case INVOKEVIRTUAL | INVOKESPECIAL | INVOKESTATIC | - INVOKEINTERFACE => - val desc = insn.asInstanceOf[MethodInsnNode].desc - val cons = Type.getArgumentTypes(desc).length + (if (insn.getOpcode == INVOKESTATIC) 0 else 1) - val prod = if (Type.getReturnType(desc) == Type.VOID_TYPE) 0 else 1 - t(cons, prod) - - case INVOKEDYNAMIC => - val desc = insn.asInstanceOf[InvokeDynamicInsnNode].desc - val cons = Type.getArgumentTypes(desc).length - val prod = if (Type.getReturnType(desc) == Type.VOID_TYPE) 0 else 1 - t(cons, prod) + INVOKEINTERFACE => invokeConsProd(insn.asInstanceOf[MethodInsnNode].desc, insn, forClassfile) + + case INVOKEDYNAMIC => invokeConsProd(insn.asInstanceOf[InvokeDynamicInsnNode].desc, insn, forClassfile) case NEW => t(0, 1) @@ -261,5 +335,4 @@ object InstructionStackEffect { IFNONNULL => t(1, 0) } } - } |