diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala | 773 |
1 files changed, 372 insertions, 401 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 7c46d648fe..e50b73dcc3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -3,17 +3,16 @@ * @author Martin Odersky */ -package scala.tools.nsc +package scala +package tools.nsc package backend.jvm -import java.nio.ByteBuffer import scala.collection.{ mutable, immutable } import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer } import scala.tools.nsc.symtab._ -import scala.tools.nsc.io.AbstractFile - import scala.tools.asm import asm.Label +import scala.annotation.tailrec /** * @author Iulian Dragos (version 1.0, FJBG-based implementation) @@ -27,11 +26,25 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { import icodes.opcodes._ import definitions._ + // Strangely I can't find this in the asm code + // 255, but reserving 1 for "this" + final val MaximumJvmParameters = 254 + val phaseName = "jvm" /** Create a new phase */ override def newPhase(p: Phase): Phase = new AsmPhase(p) + /** From the reference documentation of the Android SDK: + * The `Parcelable` interface identifies classes whose instances can be written to and restored from a `Parcel`. + * Classes implementing the `Parcelable` interface must also have a static field called `CREATOR`, + * which is an object implementing the `Parcelable.Creator` interface. + */ + private val androidFieldName = newTermName("CREATOR") + + private lazy val AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable") + private lazy val AndroidCreatorClass = rootMirror.getClassIfDefined("android.os.Parcelable$Creator") + /** JVM code generation phase */ class AsmPhase(prev: Phase) extends ICodePhase(prev) { @@ -39,7 +52,25 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { override def erasedTypes = true def apply(cls: IClass) = sys.error("no implementation") - val BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo") + // An AsmPhase starts and ends within a Run, thus the caches in question will get populated and cleared within a Run, too), SI-7422 + javaNameCache.clear() + javaNameCache ++= List( + NothingClass -> binarynme.RuntimeNothing, + RuntimeNothingClass -> binarynme.RuntimeNothing, + NullClass -> binarynme.RuntimeNull, + RuntimeNullClass -> binarynme.RuntimeNull + ) + + // unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names. + reverseJavaName.clear() + reverseJavaName ++= List( + binarynme.RuntimeNothing.toString() -> RuntimeNothingClass, // RuntimeNothingClass is the bytecode-level return type of Scala methods with Nothing return-type. + binarynme.RuntimeNull.toString() -> RuntimeNullClass + ) + + // Lazy val; can't have eager vals in Phase constructors which may + // cause cycles before Global has finished initialization. + lazy val BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo") private def initBytecodeWriter(entryPoints: List[IClass]): BytecodeWriter = { settings.outputDirs.getSingleOutput match { @@ -61,29 +92,16 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { new DirectToJarfileWriter(f.file) - case _ => - if (settings.Ygenjavap.isDefault) { - if(settings.Ydumpclasses.isDefault) - new ClassBytecodeWriter { } - else - new ClassBytecodeWriter with DumpBytecodeWriter { } - } - else new ClassBytecodeWriter with JavapBytecodeWriter { } - - // TODO A ScalapBytecodeWriter could take asm.util.Textifier as starting point. - // Three areas where javap ouput is less than ideal (e.g. when comparing versions of the same classfile) are: - // (a) unreadable pickle; - // (b) two constant pools, while having identical contents, are displayed differently due to physical layout. - // (c) stack maps (classfile version 50 and up) are displayed in encoded form by javap, their expansion makes more sense instead. + case _ => factoryNonJarBytecodeWriter() } } override def run() { - if (settings.debug.value) + if (settings.debug) inform("[running phase " + name + " on icode]") - if (settings.Xdce.value) + if (settings.Xdce) for ((sym, cls) <- icodes.classes if inliner.isClosureClass(sym) && !deadCode.liveClosures(sym)) { log(s"Optimizer eliminated ${sym.fullNameString}") icodes.classes -= sym @@ -99,41 +117,41 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { "Such classes will overwrite one another on case-insensitive filesystems.") } - debuglog("Created new bytecode generator for " + classes.size + " classes.") + debuglog(s"Created new bytecode generator for ${classes.size} classes.") val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint) - val plainCodeGen = new JPlainBuilder(bytecodeWriter) - val mirrorCodeGen = new JMirrorBuilder(bytecodeWriter) - val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter) - - while(!sortedClasses.isEmpty) { - val c = sortedClasses.head + val needsOutfile = bytecodeWriter.isInstanceOf[ClassBytecodeWriter] + val plainCodeGen = new JPlainBuilder( bytecodeWriter, needsOutfile) + val mirrorCodeGen = new JMirrorBuilder( bytecodeWriter, needsOutfile) + val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter, needsOutfile) + def emitFor(c: IClass) { if (isStaticModule(c.symbol) && isTopLevelModule(c.symbol)) { - if (c.symbol.companionClass == NoSymbol) { - mirrorCodeGen.genMirrorClass(c.symbol, c.cunit) - } else { - log("No mirror class for module with linked class: " + c.symbol.fullName) - } + if (c.symbol.companionClass == NoSymbol) + mirrorCodeGen genMirrorClass (c.symbol, c.cunit) + else + log(s"No mirror class for module with linked class: ${c.symbol.fullName}") } + plainCodeGen genClass c + if (c.symbol hasAnnotation BeanInfoAttr) beanInfoCodeGen genBeanInfoClass c + } - plainCodeGen.genClass(c) - - if (c.symbol hasAnnotation BeanInfoAttr) { - beanInfoCodeGen.genBeanInfoClass(c) + while (!sortedClasses.isEmpty) { + val c = sortedClasses.head + try emitFor(c) + catch { + case e: FileConflictException => + c.cunit.error(c.symbol.pos, s"error writing ${c.symbol}: ${e.getMessage}") } - sortedClasses = sortedClasses.tail classes -= c.symbol // GC opportunity } bytecodeWriter.close() - classes.clear() - reverseJavaName.clear() /* don't javaNameCache.clear() because that causes the following tests to fail: * test/files/run/macro-repl-dontexpand.scala * test/files/jvm/interpreter.scala - * TODO but why? what use could javaNameCache possibly see once GenJVM is over? + * TODO but why? what use could javaNameCache possibly see once GenASM is over? */ /* TODO After emitting all class files (e.g., in a separate compiler phase) ASM can perform bytecode verification: @@ -152,19 +170,10 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { var pickledBytes = 0 // statistics - // Don't put this in per run caches. Contains entries for classes as well as members. - val javaNameCache = new mutable.WeakHashMap[Symbol, Name]() ++= List( - NothingClass -> binarynme.RuntimeNothing, - RuntimeNothingClass -> binarynme.RuntimeNothing, - NullClass -> binarynme.RuntimeNull, - RuntimeNullClass -> binarynme.RuntimeNull - ) + val javaNameCache = perRunCaches.newMap[Symbol, Name]() // unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names. - val reverseJavaName = mutable.Map.empty[String, Symbol] ++= List( - binarynme.RuntimeNothing.toString() -> RuntimeNothingClass, // RuntimeNothingClass is the bytecode-level return type of Scala methods with Nothing return-type. - binarynme.RuntimeNull.toString() -> RuntimeNullClass - ) + val reverseJavaName = perRunCaches.newMap[String, Symbol]() private def mkFlags(args: Int*) = args.foldLeft(0)(_ | _) private def hasPublicBitSet(flags: Int) = (flags & asm.Opcodes.ACC_PUBLIC) != 0 @@ -248,7 +257,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } def isTopLevelModule(sym: Symbol): Boolean = - afterPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass } + exitingPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass } def isStaticModule(sym: Symbol): Boolean = { sym.isModuleClass && !sym.isImplClass && !sym.isLifted @@ -283,7 +292,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { def inameToSymbol(iname: String): Symbol = { val name = global.newTypeName(iname) val res0 = - if (nme.isModuleName(name)) rootMirror.getModule(nme.stripModuleSuffix(name)) + if (nme.isModuleName(name)) rootMirror.getModule(name.dropModule) else rootMirror.getClassByName(name.replace('/', '.')) // TODO fails for inner classes (but this hasn't been tested). assert(res0 != NoSymbol) val res = jsymbol(res0) @@ -368,7 +377,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { private val classfileVersion: Int = settings.target.value match { case "jvm-1.5" => asm.Opcodes.V1_5 - case "jvm-1.5-asm" => asm.Opcodes.V1_5 case "jvm-1.6" => asm.Opcodes.V1_6 case "jvm-1.7" => asm.Opcodes.V1_7 } @@ -396,9 +404,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } /** basic functionality for class file building */ - abstract class JBuilder(bytecodeWriter: BytecodeWriter) { + abstract class JBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) { - val EMPTY_JTYPE_ARRAY = Array.empty[asm.Type] val EMPTY_STRING_ARRAY = Array.empty[String] val mdesc_arglessvoid = "()V" @@ -443,19 +450,22 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } def createJAttribute(name: String, b: Array[Byte], offset: Int, len: Int): asm.Attribute = { - val dest = new Array[Byte](len); - System.arraycopy(b, offset, dest, 0, len); + val dest = new Array[Byte](len) + System.arraycopy(b, offset, dest, 0, len) new asm.CustomAttr(name, dest) } // ----------------------------------------------------------------------------------------- - // utitilies useful when emitting plain, mirror, and beaninfo classes. + // utilities useful when emitting plain, mirror, and beaninfo classes. // ----------------------------------------------------------------------------------------- def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) { try { val arr = jclass.toByteArray() - bytecodeWriter.writeClass(label, jclassName, arr, sym) + val outF: scala.tools.nsc.io.AbstractFile = { + if(needsOutfile) getFile(sym, jclassName, ".class") else null + } + bytecodeWriter.writeClass(label, jclassName, arr, outF) } catch { case e: java.lang.RuntimeException if(e.getMessage() == "Class file too large!") => // TODO check where ASM throws the equivalent of CodeSizeTooBigException @@ -466,7 +476,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { /** Specialized array conversion to prevent calling * java.lang.reflect.Array.newInstance via TraversableOnce.toArray */ - def mkArray(xs: Traversable[asm.Type]): Array[asm.Type] = { val a = new Array[asm.Type](xs.size); xs.copyToArray(a); a } def mkArray(xs: Traversable[String]): Array[String] = { val a = new Array[String](xs.size); xs.copyToArray(a); a } // ----------------------------------------------------------------------------------------- @@ -509,14 +518,14 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { */ def javaName(sym: Symbol): String = { - /** + /* * Checks if given symbol corresponds to inner class/object and add it to innerClassBuffer * * Note: This method is called recursively thus making sure that we add complete chain * of inner class all until root class. */ def collectInnerClass(s: Symbol): Unit = { - // TODO: some beforeFlatten { ... } which accounts for + // TODO: some enteringFlatten { ... } which accounts for // being nested in parameterized classes (if we're going to selectively flatten.) val x = innerClassSymbolFor(s) if(x ne NoSymbol) { @@ -531,7 +540,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { collectInnerClass(sym) - var hasInternalName = (sym.isClass || (sym.isModule && !sym.isMethod)) + val hasInternalName = sym.isClass || sym.isModuleNotMethod val cachedJN = javaNameCache.getOrElseUpdate(sym, { if (hasInternalName) { sym.javaBinaryName } else { sym.javaSimpleName } @@ -541,12 +550,18 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { val internalName = cachedJN.toString() val trackedSym = jsymbol(sym) reverseJavaName.get(internalName) match { - case None => + case Some(oldsym) if oldsym.exists && trackedSym.exists => + assert( + // In contrast, neither NothingClass nor NullClass show up bytecode-level. + (oldsym == trackedSym) || (oldsym == RuntimeNothingClass) || (oldsym == RuntimeNullClass) || (oldsym.isModuleClass && (oldsym.sourceModule == trackedSym.sourceModule)), + s"""|Different class symbols have the same bytecode-level internal name: + | name: $internalName + | oldsym: ${oldsym.fullNameString} + | tracked: ${trackedSym.fullNameString} + """.stripMargin + ) + case _ => reverseJavaName.put(internalName, trackedSym) - case Some(oldsym) => - assert((oldsym == trackedSym) || (oldsym == RuntimeNothingClass) || (oldsym == RuntimeNullClass) || - (oldsym.isModuleClass && (oldsym.sourceModule == trackedSym.sourceModule)), // In contrast, neither NothingClass nor NullClass show up bytecode-level. - "how can getCommonSuperclass() do its job if different class symbols get the same bytecode-level internal name: " + internalName) } } @@ -588,7 +603,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { def javaType(s: Symbol): asm.Type = { if (s.isMethod) { - val resT: asm.Type = if (s.isClassConstructor) asm.Type.VOID_TYPE else javaType(s.tpe.resultType); + val resT: asm.Type = if (s.isClassConstructor) asm.Type.VOID_TYPE else javaType(s.tpe.resultType) asm.Type.getMethodType( resT, (s.tpe.paramTypes map javaType): _*) } else { javaType(s.tpe) } } @@ -598,9 +613,9 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) } def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor) { - /** The outer name for this inner class. Note that it returns null - * when the inner class should not get an index in the constant pool. - * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS. + /* The outer name for this inner class. Note that it returns null + * when the inner class should not get an index in the constant pool. + * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS. */ def outerName(innerSym: Symbol): String = { if (innerSym.originalEnclosingMethod != NoSymbol) @@ -619,7 +634,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { innerSym.rawname + innerSym.moduleSuffix // add inner classes which might not have been referenced yet - afterErasure { + exitingErasure { for (sym <- List(csym, csym.linkedClassOfClass); m <- sym.info.decls.map(innerClassSymbolFor) if m.isClass) innerClassBuffer += m } @@ -681,7 +696,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { /** functionality for building plain and mirror classes */ - abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) { + abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JBuilder(bytecodeWriter, needsOutfile) { def debugLevel = settings.debuginfo.indexOfChoice @@ -793,7 +808,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { // without it. This is particularly bad because the availability of // generic information could disappear as a consequence of a seemingly // unrelated change. - settings.Ynogenericsig.value + settings.Ynogenericsig || sym.isArtifact || sym.isLiftedMethod || sym.isBridge @@ -810,7 +825,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { if (!needsGenericSignature(sym)) { return null } - val memberTpe = beforeErasure(owner.thisType.memberInfo(sym)) + val memberTpe = enteringErasure(owner.thisType.memberInfo(sym)) val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe) if (jsOpt.isEmpty) { return null } @@ -823,14 +838,14 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { catch { case _: Throwable => false } } - if (settings.Xverify.value) { + if (settings.Xverify) { // Run the signature parser to catch bogus signatures. val isValidSignature = wrap { // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser) - import scala.tools.asm.util.SignatureChecker - if (sym.isMethod) { SignatureChecker checkMethodSignature sig } // requires asm-util.jar - else if (sym.isTerm) { SignatureChecker checkFieldSignature sig } - else { SignatureChecker checkClassSignature sig } + import scala.tools.asm.util.CheckClassAdapter + if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } // requires asm-util.jar + else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig } + else { CheckClassAdapter checkClassSignature sig } } if(!isValidSignature) { @@ -844,7 +859,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } if ((settings.check containsName phaseName)) { - val normalizedTpe = beforeErasure(erasure.prepareSigMap(memberTpe)) + val normalizedTpe = enteringErasure(erasure.prepareSigMap(memberTpe)) val bytecodeTpe = owner.thisType.memberInfo(sym) if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) { getCurrentCUnit().warning(sym.pos, @@ -863,9 +878,9 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { - val ca = new Array[Char](bytes.size) + val ca = new Array[Char](bytes.length) var idx = 0 - while(idx < bytes.size) { + while(idx < bytes.length) { val b: Byte = bytes(idx) assert((b & ~0x7f) == 0) ca(idx) = b.asInstanceOf[Char] @@ -882,7 +897,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { var prevOffset = 0 var offset = 0 var encLength = 0 - while(offset < bSeven.size) { + while(offset < bSeven.length) { val deltaEncLength = (if(bSeven(offset) == 0) 2 else 1) val newEncLength = encLength.toLong + deltaEncLength if(newEncLength >= 65535) { @@ -1034,9 +1049,9 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { val paramJavaTypes: List[asm.Type] = methodInfo.paramTypes map javaType // val paramNames = 0 until paramJavaTypes.length map ("x_" + _) - /** Forwarders must not be marked final, - * as the JVM will not allow redefinition of a final static method, - * and we don't know what classes might be subclassing the companion class. See SI-4827. + /* Forwarders must not be marked final, + * as the JVM will not allow redefinition of a final static method, + * and we don't know what classes might be subclassing the companion class. See SI-4827. */ // TODO: evaluate the other flags we might be dropping on the floor here. // TODO: ACC_SYNTHETIC ? @@ -1099,7 +1114,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { debuglog("Dumping mirror class for object: " + moduleClass) val linkedClass = moduleClass.companionClass - val linkedModule = linkedClass.companionSymbol lazy val conflictingNames: Set[Name] = { (linkedClass.info.members collect { case sym if sym.name.isTermName => sym.name }).toSet } @@ -1125,16 +1139,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { trait JAndroidBuilder { self: JPlainBuilder => - /** From the reference documentation of the Android SDK: - * The `Parcelable` interface identifies classes whose instances can be written to and restored from a `Parcel`. - * Classes implementing the `Parcelable` interface must also have a static field called `CREATOR`, - * which is an object implementing the `Parcelable.Creator` interface. - */ - private val androidFieldName = newTermName("CREATOR") - - private lazy val AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable") - private lazy val AndroidCreatorClass = rootMirror.getClassIfDefined("android.os.Parcelable$Creator") - def isAndroidParcelableClass(sym: Symbol) = (AndroidParcelableInterface != NoSymbol) && (sym.parentSymbols contains AndroidParcelableInterface) @@ -1142,13 +1146,13 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { /* Typestate: should be called before emitting fields (because it adds an IField to the current IClass). */ def addCreatorCode(block: BasicBlock) { val fieldSymbol = ( - clasz.symbol.newValue(newTermName(androidFieldName), NoPosition, Flags.STATIC | Flags.FINAL) + clasz.symbol.newValue(androidFieldName, NoPosition, Flags.STATIC | Flags.FINAL) setInfo AndroidCreatorClass.tpe ) val methodSymbol = definitions.getMember(clasz.symbol.companionModule, androidFieldName) clasz addField new IField(fieldSymbol) - block emit CALL_METHOD(methodSymbol, Static(false)) - block emit STORE_FIELD(fieldSymbol, true) + block emit CALL_METHOD(methodSymbol, Static(onInstance = false)) + block emit STORE_FIELD(fieldSymbol, isStatic = true) } def legacyAddCreatorCode(clinit: asm.MethodVisitor) { @@ -1157,7 +1161,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { jclass.visitField( PublicStaticFinal, - androidFieldName, + androidFieldName.toString, tdesc_creator, null, // no java-generic-signature null // no initial value @@ -1177,7 +1181,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { clinit.visitMethodInsn( asm.Opcodes.INVOKEVIRTUAL, moduleName, - androidFieldName, + androidFieldName.toString, asm.Type.getMethodDescriptor(creatorType, Array.empty[asm.Type]: _*) ) @@ -1185,7 +1189,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { clinit.visitFieldInsn( asm.Opcodes.PUTSTATIC, thisName, - androidFieldName, + androidFieldName.toString, tdesc_creator ) } @@ -1242,8 +1246,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { case class BlockInteval(start: BasicBlock, end: BasicBlock) /** builder of plain classes */ - class JPlainBuilder(bytecodeWriter: BytecodeWriter) - extends JCommonBuilder(bytecodeWriter) + class JPlainBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) + extends JCommonBuilder(bytecodeWriter, needsOutfile) with JAndroidBuilder { val MIN_SWITCH_DENSITY = 0.7 @@ -1267,14 +1271,12 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { // Additional interface parents based on annotations and other cues def newParentForAttr(attr: Symbol): Option[Symbol] = attr match { - case SerializableAttr => Some(SerializableClass) - case CloneableAttr => Some(CloneableClass) case RemoteAttr => Some(RemoteInterfaceClass) case _ => None } - /** Drop redundant interfaces (ones which are implemented by some other parent) from the immediate parents. - * This is important on Android because there is otherwise an interface explosion. + /* Drop redundant interfaces (ones which are implemented by some other parent) from the immediate parents. + * This is important on Android because there is otherwise an interface explosion. */ def minimizeInterfaces(lstIfaces: List[Symbol]): List[Symbol] = { var rest = lstIfaces @@ -1292,7 +1294,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } val ps = c.symbol.info.parents - val superInterfaces0: List[Symbol] = if(ps.isEmpty) Nil else c.symbol.mixinClasses; + val superInterfaces0: List[Symbol] = if(ps.isEmpty) Nil else c.symbol.mixinClasses val superInterfaces = (superInterfaces0 ++ c.symbol.annotations.flatMap(ann => newParentForAttr(ann.symbol))).distinct if(superInterfaces.isEmpty) EMPTY_STRING_ARRAY @@ -1317,7 +1319,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { thisName = javaName(c.symbol) // the internal name of the class being emitted val ps = c.symbol.info.parents - val superClass: String = if(ps.isEmpty) JAVA_LANG_OBJECT.getInternalName else javaName(ps.head.typeSymbol); + val superClass: String = if(ps.isEmpty) JAVA_LANG_OBJECT.getInternalName else javaName(ps.head.typeSymbol) val ifaces = getSuperInterfaces(c) @@ -1364,14 +1366,14 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { for (constructor <- c.lookupStaticCtor) { addStaticInit(Some(constructor)) } - val skipStaticForwarders = (c.symbol.isInterface || settings.noForwarders.value) + val skipStaticForwarders = (c.symbol.isInterface || settings.noForwarders) if (!skipStaticForwarders) { val lmoc = c.symbol.companionModule // add static forwarders if there are no name conflicts; see bugs #363 and #1735 if (lmoc != NoSymbol) { // it must be a top level class (name contains no $s) val isCandidateForForwarders = { - afterPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass } + exitingPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass } } if (isCandidateForForwarders) { log("Adding static forwarders from '%s' to implementations in '%s'".format(c.symbol, lmoc)) @@ -1400,7 +1402,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { addInnerClasses(clasz.symbol, jclass) jclass.visitEnd() writeIfNotTooBig("" + c.symbol.name, thisName, jclass, c.symbol) - } /** @@ -1432,7 +1433,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { assert(enclClass.isClass, enclClass) val sym = enclClass.primaryConstructor if (sym == NoSymbol) { - log("Ran out of room looking for an enclosing method for %s: no constructor here.".format(enclClass, clazz)) + log("Ran out of room looking for an enclosing method for %s: no constructor here.".format(enclClass)) } else { debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, enclClass)) res = EnclMethodEntry(javaName(enclClass), javaName(sym), javaType(sym)) @@ -1483,6 +1484,11 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { if (m.symbol.isStaticConstructor || definitions.isGetClass(m.symbol)) return + if (m.params.size > MaximumJvmParameters) { + getCurrentCUnit().error(m.symbol.pos, s"Platform restriction: a parameter list's length cannot exceed $MaximumJvmParameters.") + return + } + debuglog("Generating method " + m.symbol.fullName) method = m computeLocalVarsIndex(m) @@ -1607,19 +1613,20 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { if (isStaticModule(clasz.symbol)) { // call object's private ctor from static ctor lastBlock emit NEW(REFERENCE(m.symbol.enclClass)) - lastBlock emit CALL_METHOD(m.symbol.enclClass.primaryConstructor, Static(true)) + lastBlock emit CALL_METHOD(m.symbol.enclClass.primaryConstructor, Static(onInstance = true)) } if (isParcelableClass) { addCreatorCode(lastBlock) } lastBlock emit RETURN(UNIT) - lastBlock.close + lastBlock.close() - method = m + method = m jmethod = clinitMethod jMethodName = CLASS_CONSTRUCTOR_NAME jmethod.visitCode() - genCode(m, false, true) + computeLocalVarsIndex(m) + genCode(m, emitVars = false, isStatic = true) jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments jmethod.visitEnd() @@ -1675,7 +1682,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { val kind = toTypeKind(const.typeValue) val toPush: asm.Type = if (kind.isValueType) classLiteral(kind) - else javaType(kind); + else javaType(kind) mv.visitLdcInsn(toPush) case EnumTag => @@ -1698,12 +1705,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { */ object jcode { - import asm.Opcodes; - - def aconst(cst: AnyRef) { - if (cst == null) { jmethod.visitInsn(Opcodes.ACONST_NULL) } - else { jmethod.visitLdcInsn(cst) } - } + import asm.Opcodes final def boolconst(b: Boolean) { iconst(if(b) 1 else 0) } @@ -1798,8 +1800,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) } - def emitIF(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF, label) } - def emitIF_ICMP(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP, label) } + def emitIF(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF(), label) } + def emitIF_ICMP(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP(), label) } def emitIF_ACMP(cond: TestOp, label: asm.Label) { assert((cond == EQ) || (cond == NE), cond) val opc = (if(cond == EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE) @@ -1855,7 +1857,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { val keyMax = keys(keys.length - 1) val isDenseEnough: Boolean = { - /** Calculate in long to guard against overflow. TODO what overflow??? */ + /* Calculate in long to guard against overflow. TODO what overflow??? */ val keyRangeD: Double = (keyMax.asInstanceOf[Long] - keyMin + 1).asInstanceOf[Double] val klenD: Double = keys.length val kdensity: Double = (klenD / keyRangeD) @@ -1867,10 +1869,10 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { // use a table in which holes are filled with defaultBranch. val keyRange = (keyMax - keyMin + 1) val newBranches = new Array[asm.Label](keyRange) - var oldPos = 0; + var oldPos = 0 var i = 0 while(i < keyRange) { - val key = keyMin + i; + val key = keyMin + i if (keys(oldPos) == key) { newBranches(i) = branches(oldPos) oldPos += 1 @@ -1990,7 +1992,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { // Part 2 of genCode(): demarcating exception handler boundaries (visitTryCatchBlock() must be invoked before visitLabel() in genBlock()) // ------------------------------------------------------------------------------------------------------------ - /**Generate exception handlers for the current method. + /* Generate exception handlers for the current method. * * Quoting from the JVMS 4.7.3 The Code Attribute * The items of the Code_attribute structure are as follows: @@ -2013,16 +2015,16 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { */ def genExceptionHandlers() { - /** Return a list of pairs of intervals where the handler is active. - * Each interval is closed on both ends, ie. inclusive both in the left and right endpoints: [start, end]. - * Preconditions: - * - e.covered non-empty - * Postconditions for the result: - * - always non-empty - * - intervals are sorted as per `linearization` - * - the argument's `covered` blocks have been grouped into maximally contiguous intervals, - * ie. between any two intervals in the result there is a non-empty gap. - * - each of the `covered` blocks in the argument is contained in some interval in the result + /* Return a list of pairs of intervals where the handler is active. + * Each interval is closed on both ends, ie. inclusive both in the left and right endpoints: [start, end]. + * Preconditions: + * - e.covered non-empty + * Postconditions for the result: + * - always non-empty + * - intervals are sorted as per `linearization` + * - the argument's `covered` blocks have been grouped into maximally contiguous intervals, + * ie. between any two intervals in the result there is a non-empty gap. + * - each of the `covered` blocks in the argument is contained in some interval in the result */ def intervals(e: ExceptionHandler): List[BlockInteval] = { assert(e.covered.nonEmpty, e) @@ -2069,7 +2071,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { // TODO in that case, ExceptionHandler.cls doesn't go through javaName(). What if cls is an inner class? for (e <- this.method.exh ; if e.covered.nonEmpty ; p <- intervals(e)) { debuglog("Adding exception handler " + e + "at block: " + e.startBlock + " for " + method + - " from: " + p.start + " to: " + p.end + " catching: " + e.cls); + " from: " + p.start + " to: " + p.end + " catching: " + e.cls) val cls: String = if (e.cls == NoSymbol || e.cls == ThrowableClass) null else javaName(e.cls) jmethod.visitTryCatchBlock(labels(p.start), linNext(p.end), labels(e.startBlock), cls) @@ -2093,8 +2095,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { def overlaps(that: Interval): Boolean = { !(this.precedes(that) || that.precedes(this)) } def mergeWith(that: Interval): Interval = { - val newStart = if(this.start <= that.start) this.lstart else that.lstart; - val newEnd = if(this.end <= that.end) that.lend else this.lend; + val newStart = if(this.start <= that.start) this.lstart else that.lstart + val newEnd = if(this.end <= that.end) that.lend else this.lend Interval(newStart, newEnd) } @@ -2150,7 +2152,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { def getMerged(): scala.collection.Map[Local, List[Interval]] = { // TODO should but isn't: unbalanced start(s) of scope(s) - val shouldBeEmpty = pending filter { p => val Pair(k, st) = p; st.nonEmpty }; + val shouldBeEmpty = pending filter { p => val Pair(_, st) = p; st.nonEmpty } val merged = mutable.Map[Local, List[Interval]]() def addToMerged(lv: Local, start: Label, end: Label) { val intv = Interval(start, end) @@ -2168,10 +2170,10 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { if(merged.isDefinedAt(k)) { val balancedStart = merged(k).head.lstart if(balancedStart.getOffset < start.getOffset) { - start = balancedStart; + start = balancedStart } } - val endOpt: Option[Label] = for(ranges <- merged.get(k)) yield ranges.last.lend; + val endOpt: Option[Label] = for(ranges <- merged.get(k)) yield ranges.last.lend val end = endOpt.getOrElse(onePastLast) addToMerged(k, start, end) } @@ -2204,7 +2206,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { for(Pair(local, ranges) <- scoping.getMerged()) { var name = javaName(local.sym) if (name == null) { - anonCounter += 1; + anonCounter += 1 name = "<anon" + anonCounter + ">" } for(intrvl <- ranges) { @@ -2213,7 +2215,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } // quest for deterministic output that Map.toList doesn't provide (so that ant test.stability doesn't complain). val srtd = fltnd.sortBy { kr => - val Triple(name: String, local: Local, intrvl: Interval) = kr + val Triple(name: String, _, intrvl: Interval) = kr Triple(intrvl.start, intrvl.end - intrvl.start, name) // ie sort by (start, length, name) } @@ -2244,13 +2246,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { case x :: y :: ys => nextBlock = y; genBlock(x); genBlocks(y :: ys) } - def isAccessibleFrom(target: Symbol, site: Symbol): Boolean = { - target.isPublic || target.isProtected && { - (site.enclClass isSubClass target.enclClass) || - (site.enclosingPackage == target.privateWithin) - } - } // end of genCode()'s isAccessibleFrom() - def genCallMethod(call: CALL_METHOD) { val CALL_METHOD(method, style) = call val siteSymbol = clasz.symbol @@ -2333,6 +2328,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { import asm.Opcodes (instr.category: @scala.annotation.switch) match { + case icodes.localsCat => def genLocalInstr() = (instr: @unchecked) match { case THIS(_) => jmethod.visitVarInsn(Opcodes.ALOAD, 0) @@ -2364,14 +2360,14 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { scoping.popScope(lv, end, instr.pos) } } - genLocalInstr + genLocalInstr() case icodes.stackCat => def genStackInstr() = (instr: @unchecked) match { case LOAD_MODULE(module) => // assert(module.isModule, "Expected module: " + module) - debuglog("generating LOAD_MODULE for: " + module + " flags: " + Flags.flagsToString(module.flags)); + debuglog("generating LOAD_MODULE for: " + module + " flags: " + module.flagString) if (clasz.symbol == module.moduleClass && jMethodName != nme.readResolve.toString) { jmethod.visitVarInsn(Opcodes.ALOAD, 0) } else { @@ -2388,7 +2384,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { case LOAD_EXCEPTION(_) => () } - genStackInstr + genStackInstr() case icodes.constCat => genConstant(jmethod, instr.asInstanceOf[CONSTANT].constant) @@ -2422,11 +2418,10 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } } - genCastInstr + genCastInstr() case icodes.objsCat => def genObjsInstr() = (instr: @unchecked) match { - case BOX(kind) => val MethodNameAndType(mname, mdesc) = jBoxTo(kind) jcode.invokestatic(BoxesRunTime, mname, mdesc) @@ -2442,14 +2437,14 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { case MONITOR_ENTER() => emit(Opcodes.MONITORENTER) case MONITOR_EXIT() => emit(Opcodes.MONITOREXIT) } - genObjsInstr + genObjsInstr() case icodes.fldsCat => def genFldsInstr() = (instr: @unchecked) match { case lf @ LOAD_FIELD(field, isStatic) => - var owner = javaName(lf.hostClass) - debuglog("LOAD_FIELD with owner: " + owner + " flags: " + Flags.flagsToString(field.owner.flags)) + val owner = javaName(lf.hostClass) + debuglog("LOAD_FIELD with owner: " + owner + " flags: " + field.owner.flagString) val fieldJName = javaName(field) val fieldDescr = descriptor(field) val opc = if (isStatic) Opcodes.GETSTATIC else Opcodes.GETFIELD @@ -2463,12 +2458,12 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr) } - genFldsInstr + genFldsInstr() case icodes.mthdsCat => def genMethodsInstr() = (instr: @unchecked) match { - /** Special handling to access native Array.clone() */ + /* Special handling to access native Array.clone() */ case call @ CALL_METHOD(definitions.Array_clone, Dynamic) => val target: String = javaType(call.targetTypeKind).getInternalName jcode.invokevirtual(target, "clone", mdesc_arrayClone) @@ -2476,7 +2471,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { case call @ CALL_METHOD(method, style) => genCallMethod(call) } - genMethodsInstr + genMethodsInstr() case icodes.arraysCat => def genArraysInstr() = (instr: @unchecked) match { @@ -2485,7 +2480,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { case CREATE_ARRAY(elem, 1) => jcode newarray elem case CREATE_ARRAY(elem, dims) => jmethod.visitMultiANewArrayInsn(descriptor(ArrayN(elem, dims)), dims) } - genArraysInstr + genArraysInstr() case icodes.jumpsCat => def genJumpInstr() = (instr: @unchecked) match { @@ -2502,7 +2497,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { while (restTagss.nonEmpty) { val currLabel = labels(restBranches.head) for (cTag <- restTagss.head) { - flatKeys(k) = cTag; + flatKeys(k) = cTag flatBranches(k) = currLabel k += 1 } @@ -2515,27 +2510,19 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { jcode.emitSWITCH(flatKeys, flatBranches, defaultLabel, MIN_SWITCH_DENSITY) case JUMP(whereto) => - if (nextBlock != whereto) { + if (nextBlock != whereto) jcode goTo labels(whereto) - } else if (m.exh.exists(eh => eh.covers(b))) { // SI-6102: Determine whether eliding this JUMP results in an empty range being covered by some EH. // If so, emit a NOP in place of the elided JUMP, to avoid "java.lang.ClassFormatError: Illegal exception table range" - val isSthgLeft = b.toList.exists { - case _: LOAD_EXCEPTION => false - case _: SCOPE_ENTER => false - case _: SCOPE_EXIT => false - case _: JUMP => false - case _ => true - } - if (!isSthgLeft) { - emit(asm.Opcodes.NOP) - } + else if (newNormal.isJumpOnly(b) && m.exh.exists(eh => eh.covers(b))) { + debugwarn("Had a jump only block that wasn't collapsed") + emit(asm.Opcodes.NOP) } case CJUMP(success, failure, cond, kind) => if (kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT if (nextBlock == success) { - jcode.emitIF_ICMP(cond.negate, labels(failure)) + jcode.emitIF_ICMP(cond.negate(), labels(failure)) // .. and fall through to success label } else { jcode.emitIF_ICMP(cond, labels(success)) @@ -2543,7 +2530,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } } else if (kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) if (nextBlock == success) { - jcode.emitIF_ACMP(cond.negate, labels(failure)) + jcode.emitIF_ACMP(cond.negate(), labels(failure)) // .. and fall through to success label } else { jcode.emitIF_ACMP(cond, labels(success)) @@ -2560,7 +2547,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { else emit(Opcodes.DCMPL) } if (nextBlock == success) { - jcode.emitIF(cond.negate, labels(failure)) + jcode.emitIF(cond.negate(), labels(failure)) // .. and fall through to success label } else { jcode.emitIF(cond, labels(success)) @@ -2571,7 +2558,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { case CZJUMP(success, failure, cond, kind) => if (kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT if (nextBlock == success) { - jcode.emitIF(cond.negate, labels(failure)) + jcode.emitIF(cond.negate(), labels(failure)) } else { jcode.emitIF(cond, labels(success)) if (nextBlock != failure) { jcode goTo labels(failure) } @@ -2607,7 +2594,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { else emit(Opcodes.DCMPL) } if (nextBlock == success) { - jcode.emitIF(cond.negate, labels(failure)) + jcode.emitIF(cond.negate(), labels(failure)) } else { jcode.emitIF(cond, labels(success)) if (nextBlock != failure) { jcode goTo labels(failure) } @@ -2615,26 +2602,25 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } } - genJumpInstr + genJumpInstr() case icodes.retCat => def genRetInstr() = (instr: @unchecked) match { case RETURN(kind) => jcode emitRETURN kind case THROW(_) => emit(Opcodes.ATHROW) } - genRetInstr + genRetInstr() } } - /** + /* * Emits one or more conversion instructions based on the types given as arguments. * * @param from The type of the value to be converted into another type. * @param to The type the value will be converted into. */ def emitT2T(from: TypeKind, to: TypeKind) { - assert(isNonUnitValueTK(from), from) - assert(isNonUnitValueTK(to), to) + assert(isNonUnitValueTK(from) && isNonUnitValueTK(to), s"Cannot emit primitive conversion from $from to $to") def pickOne(opcs: Array[Int]) { val chosen = (to: @unchecked) match { @@ -2650,10 +2636,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } if(from == to) { return } - if((from == BOOL) || (to == BOOL)) { - // the only conversion involving BOOL that is allowed is (BOOL -> BOOL) - throw new Error("inconvertible types : " + from.toString() + " -> " + to.toString()) - } + // the only conversion involving BOOL that is allowed is (BOOL -> BOOL) + assert(from != BOOL && to != BOOL, s"inconvertible types : $from -> $to") if(from.isIntSizedType) { // BYTE, CHAR, SHORT, and INT. (we're done with BOOL already) @@ -2701,7 +2685,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { def genPrimitive(primitive: Primitive, pos: Position) { - import asm.Opcodes; + import asm.Opcodes primitive match { @@ -2732,7 +2716,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { abort("Unknown arithmetic primitive " + primitive) } } - genArith + genArith() // TODO Logical's 2nd elem should be declared ValueTypeKind, to better approximate its allowed values (isIntSized, its comments appears to convey) // TODO GenICode uses `toTypeKind` to define that elem, `toValueTypeKind` would be needed instead. @@ -2764,7 +2748,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { if (kind != BOOL) { emitT2T(INT, kind) } } } - genLogical + genLogical() case Shift(op, kind) => def genShift() = op match { @@ -2793,7 +2777,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { emitT2T(INT, kind) } } - genShift + genShift() case Comparison(op, kind) => def genCompare() = op match { @@ -2813,12 +2797,11 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } } - genCompare + genCompare() case Conversion(src, dst) => debuglog("Converting from: " + src + " to: " + dst) - if (dst == BOOL) { println("Illegal conversion at: " + clasz + " at: " + pos.source + ":" + pos.line) } - else { emitT2T(src, dst) } + emitT2T(src, dst) case ArrayLength(_) => emit(Opcodes.ARRAYLENGTH) @@ -2867,15 +2850,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { ////////////////////// local vars /////////////////////// - // def sizeOf(sym: Symbol): Int = sizeOf(toTypeKind(sym.tpe)) - def sizeOf(k: TypeKind): Int = if(k.isWideType) 2 else 1 - // def indexOf(m: IMethod, sym: Symbol): Int = { - // val Some(local) = m lookupLocal sym - // indexOf(local) - // } - final def indexOf(local: Local): Int = { assert(local.index >= 0, "Invalid index for: " + local + "{" + local.## + "}: ") local.index @@ -2886,7 +2862,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { * *Does not assume the parameters come first!* */ def computeLocalVarsIndex(m: IMethod) { - var idx = if (m.symbol.isStaticMember) 0 else 1; + var idx = if (m.symbol.isStaticMember) 0 else 1 for (l <- m.params) { debuglog("Index value for " + l + "{" + l.## + "}: " + idx) @@ -2905,10 +2881,10 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { /** builder of mirror classes */ - class JMirrorBuilder(bytecodeWriter: BytecodeWriter) extends JCommonBuilder(bytecodeWriter) { + class JMirrorBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JCommonBuilder(bytecodeWriter, needsOutfile) { private var cunit: CompilationUnit = _ - def getCurrentCUnit(): CompilationUnit = cunit; + def getCurrentCUnit(): CompilationUnit = cunit /** Generate a mirror class for a top-level module. A mirror class is a class * containing only static methods that forward to the corresponding method @@ -2930,7 +2906,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { JAVA_LANG_OBJECT.getInternalName, EMPTY_STRING_ARRAY) - log("Dumping mirror class for '%s'".format(mirrorName)) + log(s"Dumping mirror class for '$mirrorName'") // typestate: entering mode with valid call sequences: // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* @@ -2953,13 +2929,11 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { mirrorClass.visitEnd() writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym) } - - } // end of class JMirrorBuilder /** builder of bean info classes */ - class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) { + class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JBuilder(bytecodeWriter, needsOutfile) { /** * Generate a bean info class that describes the given class. @@ -3001,8 +2975,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { for (f <- clasz.fields if f.symbol.hasGetter; g = f.symbol.getter(clasz.symbol); - s = f.symbol.setter(clasz.symbol); - if g.isPublic && !(f.symbol.name startsWith "$") + s = f.symbol.setter(clasz.symbol) + if g.isPublic && !(f.symbol.name startsWith "$") ) { // inserting $outer breaks the bean fieldList = javaName(f.symbol) :: javaName(g) :: (if (s != NoSymbol) javaName(s) else null) :: fieldList @@ -3091,111 +3065,50 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { * TODO Eventually, these utilities should be moved to IMethod and reused from normalize() (there's nothing JVM-specific about them). */ object newNormal { - - def startsWithJump(b: BasicBlock): Boolean = { assert(b.nonEmpty, "empty block"); b.firstInstruction.isInstanceOf[JUMP] } - - /** Prune from an exception handler those covered blocks which are jump-only. */ - private def coverWhatCountsOnly(m: IMethod): Boolean = { - assert(m.hasCode, "code-less method") - - var wasReduced = false - for(h <- m.exh) { - val shouldntCover = (h.covered filter startsWithJump) - if(shouldntCover.nonEmpty) { - wasReduced = true - h.covered --= shouldntCover // not removing any block on purpose. - } - } - - wasReduced + /** + * True if a block is "jump only" which is defined + * as being a block that consists only of 0 or more instructions that + * won't make it to the JVM followed by a JUMP. + */ + def isJumpOnly(b: BasicBlock): Boolean = { + val nonICode = firstNonIcodeOnlyInstructions(b) + // by definition a block has to have a jump, conditional jump, return, or throw + assert(nonICode.hasNext, "empty block") + nonICode.next.isInstanceOf[JUMP] } - /** An exception handler is pruned provided any of the following holds: - * (1) it covers nothing (for example, this may result after removing unreachable blocks) - * (2) each block it covers is of the form: JUMP(_) - * Return true iff one or more ExceptionHandlers were removed. - * - * A caveat: removing an exception handler, for whatever reason, means that its handler code (even if unreachable) - * won't be able to cause a class-loading-exception. As a result, behavior can be different. + /** + * Returns the list of instructions in a block that follow all ICode only instructions, + * where an ICode only instruction is one that won't make it to the JVM */ - private def elimNonCoveringExh(m: IMethod): Boolean = { - assert(m.hasCode, "code-less method") - - def isRedundant(eh: ExceptionHandler): Boolean = { - (eh.cls != NoSymbol) && ( // TODO `eh.isFinallyBlock` more readable than `eh.cls != NoSymbol` - eh.covered.isEmpty - || (eh.covered forall startsWithJump) - ) - } - - var wasReduced = false - val toPrune = (m.exh.toSet filter isRedundant) - if(toPrune.nonEmpty) { - wasReduced = true - for(h <- toPrune; r <- h.blocks) { m.code.removeBlock(r) } // TODO m.code.removeExh(h) - m.exh = (m.exh filterNot toPrune) - } - - wasReduced + private def firstNonIcodeOnlyInstructions(b: BasicBlock): Iterator[Instruction] = { + def isICodeOnlyInstruction(i: Instruction) = i match { + case LOAD_EXCEPTION(_) | SCOPE_ENTER(_) | SCOPE_EXIT(_) => true + case _ => false + } + b.iterator dropWhile isICodeOnlyInstruction } - private def isJumpOnly(b: BasicBlock): Option[BasicBlock] = { - b.toList match { - case JUMP(whereto) :: rest => - assert(rest.isEmpty, "A block contains instructions after JUMP (looks like enterIgnoreMode() was itself ignored.)") + /** + * Returns the target of a block that is "jump only" which is defined + * as being a block that consists only of 0 or more instructions that + * won't make it to the JVM followed by a JUMP. + * + * @param b The basic block to examine + * @return Some(target) if b is a "jump only" block or None if it's not + */ + private def getJumpOnlyTarget(b: BasicBlock): Option[BasicBlock] = { + val nonICode = firstNonIcodeOnlyInstructions(b) + // by definition a block has to have a jump, conditional jump, return, or throw + assert(nonICode.nonEmpty, "empty block") + nonICode.next match { + case JUMP(whereto) => + assert(!nonICode.hasNext, "A block contains instructions after JUMP (looks like enterIgnoreMode() was itself ignored.)") Some(whereto) case _ => None } } - private def directSuccStar(b: BasicBlock): List[BasicBlock] = { directSuccStar(List(b)) } - - /** Transitive closure of successors potentially reachable due to normal (non-exceptional) control flow. - Those BBs in the argument are also included in the result */ - private def directSuccStar(starters: Traversable[BasicBlock]): List[BasicBlock] = { - val result = new mutable.ListBuffer[BasicBlock] - var toVisit: List[BasicBlock] = starters.toList.distinct - while(toVisit.nonEmpty) { - val h = toVisit.head - toVisit = toVisit.tail - result += h - for(p <- h.directSuccessors; if !result.contains(p) && !toVisit.contains(p)) { toVisit = p :: toVisit } - } - result.toList - } - - /** Returns: - * for single-block self-loops, the pair (start, Nil) - * for other cycles, the pair (backedge-target, basic-blocks-in-the-cycle-except-backedge-target) - * otherwise a pair consisting of: - * (a) the endpoint of a (single or multi-hop) chain of JUMPs - * (such endpoint does not start with a JUMP and therefore is not part of the chain); and - * (b) the chain (ie blocks to be removed when collapsing the chain of jumps). - * Precondition: the BasicBlock given as argument starts with an unconditional JUMP. - */ - private def finalDestination(start: BasicBlock): (BasicBlock, List[BasicBlock]) = { - assert(startsWithJump(start), "not the start of a (single or multi-hop) chain of JUMPs.") - var hops: List[BasicBlock] = Nil - var prev = start - var done = false - do { - done = isJumpOnly(prev) match { - case Some(dest) => - if (dest == start) { return (start, hops) } // leave infinite-loops in place - hops ::= prev - if (hops.contains(dest)) { - // leave infinite-loops in place - return (dest, hops filterNot (dest eq _)) - } - prev = dest; - false - case None => true - } - } while(!done) - - (prev, hops) - } - /** * Collapse a chain of "jump-only" blocks such as: * @@ -3211,7 +3124,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { * In more detail: * Starting at each of the entry points (m.startBlock, the start block of each exception handler) * rephrase those control-flow instructions targeting a jump-only block (which jumps to a final destination D) to target D. - * The blocks thus skipped are also removed from IMethod.blocks. + * The blocks thus skipped become eligible to removed by the reachability analyzer * * Rationale for this normalization: * test/files/run/private-inline.scala after -optimize is chock full of @@ -3222,106 +3135,164 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { * and thus ranges with identical (start, end) (i.e, identical after GenJVM omitted the JUMPs in question) * could be weeded out to avoid "java.lang.ClassFormatError: Illegal exception table range" * Now that visitTryCatchBlock() must be called before Labels are resolved, - * this method gets rid of the BasicBlocks described above (to recap, consisting of just a JUMP). + * renders the BasicBlocks described above (to recap, consisting of just a JUMP) unreachable. */ - private def collapseJumpOnlyBlocks(m: IMethod): Boolean = { + private def collapseJumpOnlyBlocks(m: IMethod) { assert(m.hasCode, "code-less method") - /* "start" is relative in a cycle, but we call this helper with the "first" entry-point we found. */ - def realTarget(jumpStart: BasicBlock): Map[BasicBlock, BasicBlock] = { - assert(startsWithJump(jumpStart), "not part of a jump-chain") - val Pair(dest, redundants) = finalDestination(jumpStart) - (for(skipOver <- redundants) yield Pair(skipOver, dest)).toMap - } + def rephraseGotos(detour: mutable.Map[BasicBlock, BasicBlock]) { + def lookup(b: BasicBlock) = detour.getOrElse(b, b) - def rephraseGotos(detour: Map[BasicBlock, BasicBlock]) { - for(Pair(oldTarget, newTarget) <- detour.iterator) { - if(m.startBlock == oldTarget) { - m.code.startBlock = newTarget - } - for(eh <- m.exh; if eh.startBlock == oldTarget) { - eh.setStartBlock(newTarget) - } - for(b <- m.blocks; if !detour.isDefinedAt(b)) { - val idxLast = (b.size - 1) - b.lastInstruction match { - case JUMP(whereto) => - if (whereto == oldTarget) { - b.replaceInstruction(idxLast, JUMP(newTarget)) - } - case CJUMP(succ, fail, cond, kind) => - if ((succ == oldTarget) || (fail == oldTarget)) { - b.replaceInstruction(idxLast, CJUMP(detour.getOrElse(succ, succ), - detour.getOrElse(fail, fail), - cond, kind)) - } - case CZJUMP(succ, fail, cond, kind) => - if ((succ == oldTarget) || (fail == oldTarget)) { - b.replaceInstruction(idxLast, CZJUMP(detour.getOrElse(succ, succ), - detour.getOrElse(fail, fail), - cond, kind)) - } - case SWITCH(tags, labels) => - if(labels exists (detour.isDefinedAt(_))) { - val newLabels = (labels map { lab => detour.getOrElse(lab, lab) }) - b.replaceInstruction(idxLast, SWITCH(tags, newLabels)) - } - case _ => () - } - } + m.code.startBlock = lookup(m.code.startBlock) + + for(eh <- m.exh) + eh.setStartBlock(lookup(eh.startBlock)) + + for (b <- m.blocks) { + def replaceLastInstruction(i: Instruction) = { + if (b.lastInstruction != i) { + val idxLast = b.size - 1 + debuglog(s"In block $b, replacing last instruction ${b.lastInstruction} with ${i}") + b.replaceInstruction(idxLast, i) } } - /* remove from all containers that may contain a reference to */ - def elide(redu: BasicBlock) { - assert(m.startBlock != redu, "startBlock should have been re-wired by now") - m.code.removeBlock(redu); + b.lastInstruction match { + case JUMP(whereto) => + replaceLastInstruction(JUMP(lookup(whereto))) + case CJUMP(succ, fail, cond, kind) => + replaceLastInstruction(CJUMP(lookup(succ), lookup(fail), cond, kind)) + case CZJUMP(succ, fail, cond, kind) => + replaceLastInstruction(CZJUMP(lookup(succ), lookup(fail), cond, kind)) + case SWITCH(tags, labels) => + val newLabels = (labels map lookup) + replaceLastInstruction(SWITCH(tags, newLabels)) + case _ => () } + } + } - var wasReduced = false - val entryPoints: List[BasicBlock] = m.startBlock :: (m.exh map (_.startBlock)); - - var elided = mutable.Set.empty[BasicBlock] // debug - var newTargets = mutable.Set.empty[BasicBlock] // debug - - for (ep <- entryPoints) { - var reachable = directSuccStar(ep) // this list may contain blocks belonging to jump-chains that we'll skip over - while(reachable.nonEmpty) { - val h = reachable.head - reachable = reachable.tail - if(startsWithJump(h)) { - val detour = realTarget(h) - if(detour.nonEmpty) { - wasReduced = true - reachable = (reachable filterNot (detour.keySet.contains(_))) - rephraseGotos(detour) - detour.keySet foreach elide - elided ++= detour.keySet - newTargets ++= detour.values - } + /* + * Computes a mapping from jump only block to its + * final destination which is either a non-jump-only + * block or, if it's in a jump-only block cycle, is + * itself + */ + def computeDetour: mutable.Map[BasicBlock, BasicBlock] = { + // fetch the jump only blocks and their immediate destinations + val pairs = for { + block <- m.blocks.toIterator + target <- getJumpOnlyTarget(block) + } yield(block, target) + + // mapping from a jump-only block to our current knowledge of its + // final destination. Initially it's just jump block to immediate jump + // target + val detour = mutable.Map[BasicBlock, BasicBlock](pairs.toSeq:_*) + + // for each jump-only block find its final destination + // taking advantage of the destinations we found for previous + // blocks + for (key <- detour.keySet) { + // we use the Robert Floyd's classic Tortoise and Hare algorithm + @tailrec + def findDestination(tortoise: BasicBlock, hare: BasicBlock): BasicBlock = { + if (tortoise == hare) + // cycle detected, map key to key + key + else if (detour contains hare) { + // advance hare once + val hare1 = detour(hare) + // make sure we can advance hare a second time + if (detour contains hare1) + // advance tortoise once and hare a second time + findDestination(detour(tortoise), detour(hare1)) + else + // hare1 is not in the map so it's not a jump-only block, it's the destination + hare1 + } else + // hare is not in the map so it's not a jump-only block, it's the destination + hare } + // update the mapping for key based on its final destination + detour(key) = findDestination(key, detour(key)) } + detour } - assert(newTargets.intersect(elided).isEmpty, "contradiction: we just elided the final destionation of a jump-chain") - wasReduced + val detour = computeDetour + rephraseGotos(detour) + + if (settings.debug) { + val (remappings, cycles) = detour partition {case (source, target) => source != target} + for ((source, target) <- remappings) { + debuglog(s"Will elide jump only block $source because it can be jumped around to get to $target.") + if (m.startBlock == source) debugwarn("startBlock should have been re-wired by now") + } + val sources = remappings.keySet + val targets = remappings.values.toSet + val intersection = sources intersect targets + + if (intersection.nonEmpty) debugwarn(s"contradiction: we seem to have some source and target overlap in blocks ${intersection.mkString}. Map was ${detour.mkString}") + + for ((source, _) <- cycles) { + debuglog(s"Block $source is in a do-nothing infinite loop. Did the user write 'while(true){}'?") + } + } + } + + /** + * Removes all blocks that are unreachable in a method using a standard reachability analysis. + */ + def elimUnreachableBlocks(m: IMethod) { + assert(m.hasCode, "code-less method") + + // assume nothing is reachable until we prove it can be reached + val reachable = mutable.Set[BasicBlock]() + + // the set of blocks that we know are reachable but have + // yet to be marked reachable, initially only the start block + val worklist = mutable.Set(m.startBlock) + + while (worklist.nonEmpty) { + val block = worklist.head + worklist remove block + // we know that one is reachable + reachable add block + // so are its successors, so go back around and add the ones we still + // think are unreachable + worklist ++= (block.successors filterNot reachable) + } + + // exception handlers need to be told not to cover unreachable blocks + // and exception handlers that no longer cover any blocks need to be + // removed entirely + val unusedExceptionHandlers = mutable.Set[ExceptionHandler]() + for (exh <- m.exh) { + exh.covered = exh.covered filter reachable + if (exh.covered.isEmpty) { + unusedExceptionHandlers += exh + } + } + + // remove the unusued exception handler references + if (settings.debug) + for (exh <- unusedExceptionHandlers) debuglog(s"eliding exception handler $exh because it does not cover any reachable blocks") + m.exh = m.exh filterNot unusedExceptionHandlers + + // everything not in the reachable set is unreachable, unused, and unloved. buh bye + for (b <- m.blocks filterNot reachable) { + debuglog(s"eliding block $b because it is unreachable") + m.code removeBlock b + } } def normalize(m: IMethod) { if(!m.hasCode) { return } collapseJumpOnlyBlocks(m) - var wasReduced = false; - do { - wasReduced = false - // Prune from an exception handler those covered blocks which are jump-only. - wasReduced |= coverWhatCountsOnly(m); icodes.checkValid(m) // TODO should be unnecessary now that collapseJumpOnlyBlocks(m) is in place - // Prune exception handlers covering nothing. - wasReduced |= elimNonCoveringExh(m); icodes.checkValid(m) - - // TODO see note in genExceptionHandlers about an ExceptionHandler.covered containing dead blocks (newNormal should remove them, but, where do those blocks come from?) - } while (wasReduced) - - // TODO this would be a good time to remove synthetic local vars seeing no use, don't forget to call computeLocalVarsIndex() afterwards. + if (settings.optimise) + elimUnreachableBlocks(m) + icodes checkValid m } } |