diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala | 243 |
1 files changed, 134 insertions, 109 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala index 7aadd2c466..bfd92cac5c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -8,28 +8,29 @@ 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.{MethodWriter, ClassWriter, Label, Opcodes, Type} +import scala.tools.asm.{Label, Type} +import scala.tools.asm.Opcodes._ import scala.tools.asm.tree._ import GenBCode._ -import scala.collection.convert.decorateAsScala._ -import scala.collection.convert.decorateAsJava._ -import scala.tools.nsc.backend.jvm.BTypes._ +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 + 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 == Opcodes.GOTO) Some(instruction.asInstanceOf[JumpInsnNode]) + if (instruction.getOpcode == GOTO) Some(instruction.asInstanceOf[JumpInsnNode]) else None } } @@ -49,8 +50,9 @@ object BytecodeUtils { } object VarInstruction { - def unapply(instruction: AbstractInsnNode): Option[VarInsnNode] = { - if (isVarInstruction(instruction)) Some(instruction.asInstanceOf[VarInsnNode]) + 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 } @@ -59,30 +61,46 @@ object BytecodeUtils { 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 == Opcodes.GOTO || isConditionalJump(instruction) + op == GOTO || isConditionalJump(instruction) } def isConditionalJump(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode - (op >= Opcodes.IFEQ && op <= Opcodes.IF_ACMPNE) || op == Opcodes.IFNULL || op == Opcodes.IFNONNULL + (op >= IFEQ && op <= IF_ACMPNE) || op == IFNULL || op == IFNONNULL } def isReturn(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode - op >= Opcodes.IRETURN && op <= Opcodes.RETURN + op >= IRETURN && op <= RETURN } def isLoad(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode - op >= Opcodes.ILOAD && op <= Opcodes.ALOAD + op >= ILOAD && op <= ALOAD } def isStore(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode - op >= Opcodes.ISTORE && op <= Opcodes.ASTORE + 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 isVarInstruction(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction) + def isVirtualCall(instruction: AbstractInsnNode): Boolean = { + val op = instruction.getOpcode + op == INVOKEVIRTUAL || op == INVOKEINTERFACE + } + + def isCall(instruction: AbstractInsnNode): Boolean = { + isNonVirtualCall(instruction) || isVirtualCall(instruction) + } def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0 @@ -90,27 +108,40 @@ object BytecodeUtils { methodNode.name == INSTANCE_CONSTRUCTOR_NAME || methodNode.name == CLASS_CONSTRUCTOR_NAME } - def isStaticMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STATIC) != 0 + def isPublicMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_PUBLIC) != 0 - def isAbstractMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_ABSTRACT) != 0 + def isPrivateMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_PRIVATE) != 0 - def isSynchronizedMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_SYNCHRONIZED) != 0 + def isStaticMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_STATIC) != 0 - def isNativeMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_NATIVE) != 0 + def isAbstractMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_ABSTRACT) != 0 - def isFinalClass(classNode: ClassNode): Boolean = (classNode.access & Opcodes.ACC_FINAL) != 0 + def isSynchronizedMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_SYNCHRONIZED) != 0 - def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) != 0 + def isNativeMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_NATIVE) != 0 - def isStrictfpMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STRICT) != 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 - def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = { - var result = instruction - do { result = result.getNext } - while (result != null && !isExecutable(result) && !alsoKeep(result)) - Option(result) + @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 = { @@ -124,14 +155,14 @@ object BytecodeUtils { def removeJumpAndAdjustStack(method: MethodNode, jump: JumpInsnNode) { val instructions = method.instructions val op = jump.getOpcode - if ((op >= Opcodes.IFEQ && op <= Opcodes.IFGE) || op == Opcodes.IFNULL || op == Opcodes.IFNONNULL) { + if ((op >= IFEQ && op <= IFLE) || op == IFNULL || op == IFNONNULL) { instructions.insert(jump, getPop(1)) - } else if ((op >= Opcodes.IF_ICMPEQ && op <= Opcodes.IF_ICMPLE) || op == Opcodes.IF_ACMPEQ || op == Opcodes.IF_ACMPNE) { + } 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 == Opcodes.GOTO) + assert(jump.getOpcode == GOTO) } instructions.remove(jump) } @@ -148,37 +179,61 @@ object BytecodeUtils { } def negateJumpOpcode(jumpOpcode: Int): Int = (jumpOpcode: @switch) match { - case Opcodes.IFEQ => Opcodes.IFNE - case Opcodes.IFNE => Opcodes.IFEQ + case IFEQ => IFNE + case IFNE => IFEQ + + case IFLT => IFGE + case IFGE => IFLT - case Opcodes.IFLT => Opcodes.IFGE - case Opcodes.IFGE => Opcodes.IFLT + case IFGT => IFLE + case IFLE => IFGT - case Opcodes.IFGT => Opcodes.IFLE - case Opcodes.IFLE => Opcodes.IFGT + case IF_ICMPEQ => IF_ICMPNE + case IF_ICMPNE => IF_ICMPEQ - case Opcodes.IF_ICMPEQ => Opcodes.IF_ICMPNE - case Opcodes.IF_ICMPNE => Opcodes.IF_ICMPEQ + case IF_ICMPLT => IF_ICMPGE + case IF_ICMPGE => IF_ICMPLT - case Opcodes.IF_ICMPLT => Opcodes.IF_ICMPGE - case Opcodes.IF_ICMPGE => Opcodes.IF_ICMPLT + case IF_ICMPGT => IF_ICMPLE + case IF_ICMPLE => IF_ICMPGT - case Opcodes.IF_ICMPGT => Opcodes.IF_ICMPLE - case Opcodes.IF_ICMPLE => Opcodes.IF_ICMPGT + case IF_ACMPEQ => IF_ACMPNE + case IF_ACMPNE => IF_ACMPEQ - case Opcodes.IF_ACMPEQ => Opcodes.IF_ACMPNE - case Opcodes.IF_ACMPNE => Opcodes.IF_ACMPEQ + case IFNULL => IFNONNULL + case IFNONNULL => IFNULL + } - case Opcodes.IFNULL => Opcodes.IFNONNULL - case Opcodes.IFNONNULL => Opcodes.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) Opcodes.POP else Opcodes.POP2 + val op = if (size == 1) POP else POP2 new InsnNode(op) } - def instructionResultSize(instruction: AbstractInsnNode) = InstructionResultSize(instruction) + 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]] @@ -222,29 +277,6 @@ object BytecodeUtils { } } - /** - * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM - * framework only computes these values during bytecode generation. - * - * Since there's currently no better way, we run a bytecode generator on the method and extract - * the computed values. This required changes to the ASM codebase: - * - the [[MethodWriter]] class was made public - * - accessors for maxLocals / maxStack were added to the MethodWriter class - * - * We could probably make this faster (and allocate less memory) by hacking the ASM framework - * more: create a subclass of MethodWriter with a /dev/null byteVector. Another option would be - * to create a separate visitor for computing those values, duplicating the functionality from the - * MethodWriter. - */ - def computeMaxLocalsMaxStack(method: MethodNode): Unit = { - val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS) - val excs = method.exceptions.asScala.toArray - val mw = cw.visitMethod(method.access, method.name, method.desc, method.signature, excs).asInstanceOf[MethodWriter] - method.accept(mw) - method.maxLocals = mw.getMaxLocals - method.maxStack = mw.getMaxStack - } - 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. @@ -289,34 +321,36 @@ object BytecodeUtils { } /** - * Clone the instructions in `methodNode` into a new [[InsnList]], mapping labels according to - * the `labelMap`. Returns the new instruction list and a map from old to new instructions. - */ - def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode]): (InsnList, Map[AbstractInsnNode, AbstractInsnNode]) = { - val javaLabelMap = labelMap.asJava - val result = new InsnList - var map = Map.empty[AbstractInsnNode, AbstractInsnNode] - for (ins <- methodNode.instructions.iterator.asScala) { - val cloned = ins.clone(javaLabelMap) - result add cloned - map += ((ins, cloned)) - } - (result, map) - } - - /** * 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): 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 - )).toList + def cloneLocalVariableNodes(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], calleeMethodName: String, shift: Int): List[LocalVariableNode] = { + methodNode.localVariables.iterator().asScala.map(localVariable => { + val name = + if (calleeMethodName.length + localVariable.name.length < BTypes.InlinedLocalVariablePrefixMaxLenght) { + calleeMethodName + "_" + localVariable.name + } else { + val parts = localVariable.name.split("_").toVector + val (methNames, varName) = (calleeMethodName +: parts.init, parts.last) + // keep at least 5 characters per method name + val maxNumMethNames = BTypes.InlinedLocalVariablePrefixMaxLenght / 5 + val usedMethNames = + if (methNames.length < maxNumMethNames) methNames + else { + val half = maxNumMethNames / 2 + methNames.take(half) ++ methNames.takeRight(half) + } + val charsPerMethod = BTypes.InlinedLocalVariablePrefixMaxLenght / usedMethNames.length + usedMethNames.foldLeft("")((res, methName) => res + methName.take(charsPerMethod) + "_") + varName + } + new LocalVariableNode( + name, + localVariable.desc, + localVariable.signature, + labelMap(localVariable.start), + labelMap(localVariable.end), + localVariable.index + shift) + }).toList } /** @@ -344,23 +378,14 @@ object BytecodeUtils { * method which explains the issue with such phantom values. */ def fixLoadedNothingOrNullValue(loadedType: Type, loadInstr: AbstractInsnNode, methodNode: MethodNode, bTypes: BTypes): Unit = { - if (loadedType == bTypes.coreBTypes.RT_NOTHING.toASMType) { - methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ATHROW)) - } else if (loadedType == bTypes.coreBTypes.RT_NULL.toASMType) { - methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ACONST_NULL)) - methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.POP)) + 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)) } } - /** - * A wrapper to make ASM's Analyzer a bit easier to use. - */ - class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, interpreter: Interpreter[V] = new BasicInterpreter) { - val analyzer = new Analyzer(interpreter) - analyzer.analyze(classInternalName, methodNode) - def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode) - } - 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)) } |