/* NSC -- new Scala compiler * Copyright 2005-2014 LAMP/EPFL * @author Martin Odersky */ package scala.tools.nsc package backend.jvm package opt import scala.annotation.{tailrec, switch} import scala.collection.mutable import scala.reflect.internal.util.Collections._ import scala.tools.asm.commons.CodeSizeEvaluator import scala.tools.asm.tree.analysis._ import scala.tools.asm.{Label, Type} import scala.tools.asm.Opcodes._ import scala.tools.asm.tree._ import GenBCode._ import scala.collection.JavaConverters._ import scala.tools.nsc.backend.jvm.analysis.InstructionStackEffect object BytecodeUtils { // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.9.1 final val maxJVMMethodSize = 65535 // 5% margin, more than enough for the instructions added by the inliner (store / load args, null check for instance methods) final val maxMethodSizeAfterInline = maxJVMMethodSize - (maxJVMMethodSize / 20) object Goto { def unapply(instruction: AbstractInsnNode): Option[JumpInsnNode] = { if (instruction.getOpcode == GOTO) Some(instruction.asInstanceOf[JumpInsnNode]) else None } } object JumpNonJsr { def unapply(instruction: AbstractInsnNode): Option[JumpInsnNode] = { if (isJumpNonJsr(instruction)) Some(instruction.asInstanceOf[JumpInsnNode]) else None } } object ConditionalJump { def unapply(instruction: AbstractInsnNode): Option[JumpInsnNode] = { if (isConditionalJump(instruction)) Some(instruction.asInstanceOf[JumpInsnNode]) else None } } object VarInstruction { def unapply(instruction: AbstractInsnNode): Option[(AbstractInsnNode, Int)] = { if (isLoadStoreOrRet(instruction)) Some((instruction, instruction.asInstanceOf[VarInsnNode].`var`)) else if (instruction.getOpcode == IINC) Some((instruction, instruction.asInstanceOf[IincInsnNode].`var`)) else None } } def isJumpNonJsr(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode // JSR is deprecated in classfile version 50, disallowed in 51. historically, it was used to implement finally. op == GOTO || isConditionalJump(instruction) } def isConditionalJump(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode (op >= IFEQ && op <= IF_ACMPNE) || op == IFNULL || op == IFNONNULL } def isReturn(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode op >= IRETURN && op <= RETURN } def isLoad(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode op >= ILOAD && op <= ALOAD } def isStore(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode op >= ISTORE && op <= ASTORE } def isLoadStoreOrRet(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction) || instruction.getOpcode == RET def isLoadOrStore(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction) def isNonVirtualCall(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode op == INVOKESPECIAL || op == INVOKESTATIC } def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0 def isConstructor(methodNode: MethodNode): Boolean = { methodNode.name == INSTANCE_CONSTRUCTOR_NAME || methodNode.name == CLASS_CONSTRUCTOR_NAME } def isPublicMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_PUBLIC) != 0 def isPrivateMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_PRIVATE) != 0 def isStaticMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_STATIC) != 0 def isAbstractMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_ABSTRACT) != 0 def isSynchronizedMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_SYNCHRONIZED) != 0 def isNativeMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_NATIVE) != 0 def hasCallerSensitiveAnnotation(methodNode: MethodNode): Boolean = methodNode.visibleAnnotations != null && methodNode.visibleAnnotations.asScala.exists(_.desc == "Lsun/reflect/CallerSensitive;") def isFinalClass(classNode: ClassNode): Boolean = (classNode.access & ACC_FINAL) != 0 def isInterface(classNode: ClassNode): Boolean = (classNode.access & ACC_INTERFACE) != 0 def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (ACC_FINAL | ACC_PRIVATE | ACC_STATIC)) != 0 def isStrictfpMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_STRICT) != 0 def isReference(t: Type) = t.getSort == Type.OBJECT || t.getSort == Type.ARRAY @tailrec def nextExecutableInstruction(insn: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = { val next = insn.getNext if (next == null || isExecutable(next) || alsoKeep(next)) Option(next) else nextExecutableInstruction(next, alsoKeep) } @tailrec def nextExecutableInstructionOrLabel(insn: AbstractInsnNode): Option[AbstractInsnNode] = { val next = insn.getNext if (next == null || isExecutable(next) || next.isInstanceOf[LabelNode]) Option(next) else nextExecutableInstructionOrLabel(next) } def sameTargetExecutableInstruction(a: JumpInsnNode, b: JumpInsnNode): Boolean = { // Compare next executable instead of the labels. Identifies a, b as the same target: // LabelNode(a) // LabelNode(b) // Instr nextExecutableInstruction(a.label) == nextExecutableInstruction(b.label) } def removeJumpAndAdjustStack(method: MethodNode, jump: JumpInsnNode) { val instructions = method.instructions val op = jump.getOpcode if ((op >= IFEQ && op <= IFLE) || op == IFNULL || op == IFNONNULL) { instructions.insert(jump, getPop(1)) } else if ((op >= IF_ICMPEQ && op <= IF_ICMPLE) || op == IF_ACMPEQ || op == IF_ACMPNE) { instructions.insert(jump, getPop(1)) instructions.insert(jump, getPop(1)) } else { // we can't remove JSR: its execution does not only jump, it also adds a return address to the stack assert(jump.getOpcode == GOTO) } instructions.remove(jump) } def finalJumpTarget(source: JumpInsnNode): LabelNode = { @tailrec def followGoto(label: LabelNode, seenLabels: Set[LabelNode]): LabelNode = nextExecutableInstruction(label) match { case Some(Goto(dest)) => if (seenLabels(dest.label)) dest.label else followGoto(dest.label, seenLabels + dest.label) case _ => label } followGoto(source.label, Set(source.label)) } def negateJumpOpcode(jumpOpcode: Int): Int = (jumpOpcode: @switch) match { case IFEQ => IFNE case IFNE => IFEQ case IFLT => IFGE case IFGE => IFLT case IFGT => IFLE case IFLE => IFGT case IF_ICMPEQ => IF_ICMPNE case IF_ICMPNE => IF_ICMPEQ case IF_ICMPLT => IF_ICMPGE case IF_ICMPGE => IF_ICMPLT case IF_ICMPGT => IF_ICMPLE case IF_ICMPLE => IF_ICMPGT case IF_ACMPEQ => IF_ACMPNE case IF_ACMPNE => IF_ACMPEQ case IFNULL => IFNONNULL case IFNONNULL => IFNULL } def isSize2LoadOrStore(opcode: Int): Boolean = (opcode: @switch) match { case LLOAD | DLOAD | LSTORE | DSTORE => true case _ => false } def getPop(size: Int): InsnNode = { val op = if (size == 1) POP else POP2 new InsnNode(op) } def instructionResultSize(insn: AbstractInsnNode) = InstructionStackEffect.prod(InstructionStackEffect.forClassfile(insn)) def loadZeroForTypeSort(sort: Int) = (sort: @switch) match { case Type.BOOLEAN | Type.BYTE | Type.CHAR | Type.SHORT | Type.INT => new InsnNode(ICONST_0) case Type.LONG => new InsnNode(LCONST_0) case Type.FLOAT => new InsnNode(FCONST_0) case Type.DOUBLE => new InsnNode(DCONST_0) case Type.OBJECT => new InsnNode(ACONST_NULL) } /** * The number of local variable slots used for parameters and for the `this` reference. */ def parametersSize(methodNode: MethodNode): Int = { (Type.getArgumentsAndReturnSizes(methodNode.desc) >> 2) - (if (isStaticMethod(methodNode)) 1 else 0) } def labelReferences(method: MethodNode): Map[LabelNode, Set[AnyRef]] = { val res = mutable.Map.empty[LabelNode, Set[AnyRef]] def add(l: LabelNode, ref: AnyRef) = if (res contains l) res(l) = res(l) + ref else res(l) = Set(ref) method.instructions.iterator().asScala foreach { case jump: JumpInsnNode => add(jump.label, jump) case line: LineNumberNode => add(line.start, line) case switch: LookupSwitchInsnNode => switch.labels.asScala.foreach(add(_, switch)); add(switch.dflt, switch) case switch: TableSwitchInsnNode => switch.labels.asScala.foreach(add(_, switch)); add(switch.dflt, switch) case _ => } if (method.localVariables != null) { method.localVariables.iterator().asScala.foreach(l => { add(l.start, l); add(l.end, l) }) } if (method.tryCatchBlocks != null) { method.tryCatchBlocks.iterator().asScala.foreach(l => { add(l.start, l); add(l.handler, l); add(l.end, l) }) } res.toMap } def substituteLabel(reference: AnyRef, from: LabelNode, to: LabelNode): Unit = { def substList(list: java.util.List[LabelNode]) = { foreachWithIndex(list.asScala.toList) { case (l, i) => if (l == from) list.set(i, to) } } reference match { case jump: JumpInsnNode => jump.label = to case line: LineNumberNode => line.start = to case switch: LookupSwitchInsnNode => substList(switch.labels); if (switch.dflt == from) switch.dflt = to case switch: TableSwitchInsnNode => substList(switch.labels); if (switch.dflt == from) switch.dflt = to case local: LocalVariableNode => if (local.start == from) local.start = to if (local.end == from) local.end = to case handler: TryCatchBlockNode => if (handler.start == from) handler.start = to if (handler.handler == from) handler.handler = to if (handler.end == from) handler.end = to } } def codeSizeOKForInlining(caller: MethodNode, callee: MethodNode): Boolean = { // Looking at the implementation of CodeSizeEvaluator, all instructions except tableswitch and // lookupswitch are <= 8 bytes. These should be rare enough for 8 to be an OK rough upper bound. def roughUpperBound(methodNode: MethodNode): Int = methodNode.instructions.size * 8 def maxSize(methodNode: MethodNode): Int = { val eval = new CodeSizeEvaluator(null) methodNode.accept(eval) eval.getMaxSize } (roughUpperBound(caller) + roughUpperBound(callee) > maxMethodSizeAfterInline) && (maxSize(caller) + maxSize(callee) > maxMethodSizeAfterInline) } def removeLineNumberNodes(classNode: ClassNode): Unit = { for (m <- classNode.methods.asScala) removeLineNumberNodes(m.instructions) } def removeLineNumberNodes(instructions: InsnList): Unit = { val iter = instructions.iterator() while (iter.hasNext) iter.next() match { case _: LineNumberNode => iter.remove() case _ => } } def cloneLabels(methodNode: MethodNode): Map[LabelNode, LabelNode] = { methodNode.instructions.iterator().asScala.collect({ case labelNode: LabelNode => (labelNode, newLabelNode) }).toMap } /** * Create a new [[LabelNode]] with a correctly associated [[Label]]. */ def newLabelNode: LabelNode = { val label = new Label val labelNode = new LabelNode(label) label.info = labelNode labelNode } /** * Clone the local variable descriptors of `methodNode` and map their `start` and `end` labels * according to the `labelMap`. */ def cloneLocalVariableNodes(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], prefix: String, shift: Int): List[LocalVariableNode] = { methodNode.localVariables.iterator().asScala.map(localVariable => new LocalVariableNode( prefix + localVariable.name, localVariable.desc, localVariable.signature, labelMap(localVariable.start), labelMap(localVariable.end), localVariable.index + shift )).toList } /** * Clone the local try/catch blocks of `methodNode` and map their `start` and `end` and `handler` * labels according to the `labelMap`. */ def cloneTryCatchBlockNodes(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode]): List[TryCatchBlockNode] = { methodNode.tryCatchBlocks.iterator().asScala.map(tryCatch => new TryCatchBlockNode( labelMap(tryCatch.start), labelMap(tryCatch.end), labelMap(tryCatch.handler), tryCatch.`type` )).toList } /** * This method is used by optimizer components to eliminate phantom values of instruction * that load a value of type `Nothing$` or `Null$`. Such values on the stack don't interact well * with stack map frames. * * For example, `opt.getOrElse(throw e)` is re-written to an invocation of the lambda body, a * method with return type `Nothing$`. Similarly for `opt.getOrElse(null)` and `Null$`. * * During bytecode generation this is handled by BCodeBodyBuilder.adapt. See the comment in that * method which explains the issue with such phantom values. */ def fixLoadedNothingOrNullValue(loadedType: Type, loadInstr: AbstractInsnNode, methodNode: MethodNode, bTypes: BTypes): Unit = { if (loadedType == bTypes.coreBTypes.srNothingRef.toASMType) { methodNode.instructions.insert(loadInstr, new InsnNode(ATHROW)) } else if (loadedType == bTypes.coreBTypes.srNullRef.toASMType) { methodNode.instructions.insert(loadInstr, new InsnNode(ACONST_NULL)) methodNode.instructions.insert(loadInstr, new InsnNode(POP)) } } implicit class AnalyzerExtensions[V <: Value](val analyzer: Analyzer[V]) extends AnyVal { def frameAt(instruction: AbstractInsnNode, methodNode: MethodNode): Frame[V] = analyzer.getFrames()(methodNode.instructions.indexOf(instruction)) } implicit class FrameExtensions[V <: Value](val frame: Frame[V]) extends AnyVal { /** * The value `n` positions down the stack. */ def peekStack(n: Int): V = frame.getStack(frame.getStackSize - 1 - n) /** * The index of the current stack top. */ def stackTop = frame.getLocals + frame.getStackSize - 1 /** * Gets the value at slot i, where i may be a local or a stack index. */ def getValue(i: Int): V = { if (i < frame.getLocals) frame.getLocal(i) else frame.getStack(i - frame.getLocals) } /** * Sets the value at slot i, where i may be a local or a stack index. */ def setValue(i: Int, value: V): Unit = { if (i < frame.getLocals) frame.setLocal(i, value) else frame.setStack(i - frame.getLocals, value) } } }