From 4e77c51decb7090efe11d5c7960a4cd563a14fc0 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Sun, 4 Jan 2015 13:16:41 -0800 Subject: [PATCH 01/11] TODO notes for release script --- scripts/jobs/scala-release-2.11.x-build | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/jobs/scala-release-2.11.x-build b/scripts/jobs/scala-release-2.11.x-build index 21fbb8fa76bc..09a97dc34bfb 100755 --- a/scripts/jobs/scala-release-2.11.x-build +++ b/scripts/jobs/scala-release-2.11.x-build @@ -1,4 +1,9 @@ #!/bin/bash -e +# TODO: different scripts for the different phases -- usually we don't need to bootstrap the modules, +# since we can use the previous version of scala for STARR as well as for compiling the modules (assuming it's binary compatible) +# We should move away from the complicated bootstrap and set up our release schedule so we always have a previous build that satisfies these criteria. +# (Potentially trivially, by splitting up this script, and publishing locker as if it were a real release.) + # requirements: # sbtCmd must point to sbt from sbt-extras (this is the standard on the Scala jenkins, so we only support that one) # - ~/.sonatype-curl that consists of user = USER:PASS From 85f3548b1b7254f7de555dd7c801b9b7f9101d13 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Sun, 4 Jan 2015 13:18:47 -0800 Subject: [PATCH 02/11] Bump versions for 2.11.5 release. --- build.number | 6 +++--- versions.properties | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.number b/build.number index dc85ecb77700..5f8ed6d6b663 100644 --- a/build.number +++ b/build.number @@ -1,9 +1,9 @@ #Tue Sep 11 19:21:09 CEST 2007 version.major=2 version.minor=11 -version.patch=5 +version.patch=6 # This is the -N part of a version. if it's 0, it's dropped from maven versions. version.bnum=0 -# Note: To build a release run ant with -Dbuild.release=true -# To build an RC, run ant with -Dmaven.version.suffix=-RCN +# To build a release, see scripts/jobs/scala-release-2.11.x-build +# (normally run by the eponymous job on scala-ci.typesafe.com). \ No newline at end of file diff --git a/versions.properties b/versions.properties index a474b19c5b78..9e2e6cea796e 100644 --- a/versions.properties +++ b/versions.properties @@ -4,7 +4,7 @@ # when adding new properties that influence a release, # also add them to the update.versions mechanism in build.xml, # which is used by scala-release-2.11.x in scala/jenkins-scripts -starr.version=2.11.2 +starr.version=2.11.5 starr.use.released=1 # These are the versions of the modules that go with this release. @@ -14,7 +14,7 @@ starr.use.released=1 scala.binary.version=2.11 # e.g. 2.11.0-RC1, 2.11.0, 2.11.1-RC1, 2.11.1 # this defines the dependency on scala-continuations-plugin in scala-dist's pom -scala.full.version=2.11.2 +scala.full.version=2.11.5 # external modules shipped with distribution, as specified by scala-library-all's pom scala-xml.version.number=1.0.3 From 70ed23d58f9dd2d198250d2a889b60c88e39f59a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 15 Jan 2015 11:33:54 +1000 Subject: [PATCH 03/11] SI-9089 Another REPL/FSC + specialization bug fix The enclosed test case stopped working in 2.11.5 on the back of https://github.com/scala/scala/pull/4040. The key change was that we ran all post-typer info transformers on each run of the compiler, rather than trying to reuse the results of the previous run. In that patch, I noticed one place [1] in specialization that aggressively entered specialized members into the owning scope, rather than relying on `transformInfo` to place the new members in the scope of the newly created element of the info history. I made that change after noticing that this code could actually mutated scopes of specializaed types at the parser phase, which led to fairly obscure failures. This bug is another one of these obscure failures, and has the same root cause. We effectively "double specialiaze" Function0, which trips an assertion when `method apply$mcI$sp` is found twice in a scope. I have found another spot that was directly manipulating the scope, and removed the offending code. [1] https://github.com/scala/scala/pull/4040#commitcomment-8531516 --- src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala | 1 - test/files/res/t9089.check | 4 ++++ test/files/res/t9089.res | 2 ++ test/files/res/t9089/A.scala | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 test/files/res/t9089.check create mode 100644 test/files/res/t9089.res create mode 100644 test/files/res/t9089/A.scala diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index 9c81e31ad9b1..5a31e20bf8bf 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -894,7 +894,6 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } val specMember = subst(outerEnv)(specializedOverload(owner, sym, spec)) - owner.info.decls.enter(specMember) typeEnv(specMember) = typeEnv(sym) ++ outerEnv ++ spec wasSpecializedForTypeVars(specMember) ++= spec collect { case (s, tp) if s.tpe == tp => s } diff --git a/test/files/res/t9089.check b/test/files/res/t9089.check new file mode 100644 index 000000000000..6cf64f734be2 --- /dev/null +++ b/test/files/res/t9089.check @@ -0,0 +1,4 @@ + +nsc> +nsc> +nsc> diff --git a/test/files/res/t9089.res b/test/files/res/t9089.res new file mode 100644 index 000000000000..ab5cc8534d30 --- /dev/null +++ b/test/files/res/t9089.res @@ -0,0 +1,2 @@ +t9089/A.scala +t9089/A.scala diff --git a/test/files/res/t9089/A.scala b/test/files/res/t9089/A.scala new file mode 100644 index 000000000000..bccf26963938 --- /dev/null +++ b/test/files/res/t9089/A.scala @@ -0,0 +1 @@ +object O { def f(x: => Int): Int = x } From 91435f68d280071d286c9d0e13b279293843ae7e Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Thu, 11 Dec 2014 17:32:02 +0100 Subject: [PATCH 04/11] Make ClassBType independent of the name table Each ClassBType is identified by its internalName, the fully qualified JVM class name. Before this change, the name was stored in the `chrs` array of the compiler name table (hash consed), with the idea to avoid materializing the string. However, we materialize the string anyway, because each ClassBType is stored in the classBTypeFromInternalNameMap, indexed by the string. If string equality turns out to be too slow we can use interning. For the inliner, we read classes from bytecode and create ClassBTypes for them. The names of these classes would not yet exist in the name table, so the backend would need to be able to create new names. Using Strings removes this dependency. --- .../nsc/backend/jvm/BCodeIdiomatic.scala | 4 +- .../scala/tools/nsc/backend/jvm/BTypes.scala | 62 +++---------------- .../nsc/backend/jvm/BTypesFromSymbols.scala | 6 +- 3 files changed, 12 insertions(+), 60 deletions(-) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index d58368b19d68..c3db28151b55 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -271,7 +271,7 @@ abstract class BCodeIdiomatic extends SubComponent { assert(from != BOOL && to != BOOL, s"inconvertible types : $from -> $to") // We're done with BOOL already - from match { + (from: @unchecked) match { // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" @@ -361,7 +361,7 @@ abstract class BCodeIdiomatic extends SubComponent { assert(elem.isNonVoidPrimitiveType) val rand = { // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" - elem match { + (elem: @unchecked) match { case BOOL => Opcodes.T_BOOLEAN case BYTE => Opcodes.T_BYTE case SHORT => Opcodes.T_SHORT diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 7defd7c873eb..879f2349187a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -31,12 +31,6 @@ abstract class BTypes { */ protected val classBTypeFromInternalNameMap: collection.concurrent.Map[String, ClassBType] - /** - * The string represented by the `offset` / `length` values of a ClassBType, see comment of that - * class. - */ - protected def internalNameString(offset: Int, lenght: Int): String - /** * Obtain a previously constructed ClassBType for a given internal name. */ @@ -52,7 +46,7 @@ abstract class BTypes { * A BType is either a primitive type, a ClassBType, an ArrayBType of one of these, or a MethodType * referring to BTypes. */ - /*sealed*/ trait BType { // Not sealed for now due to SI-8546 + sealed trait BType { final override def toString: String = this match { case UNIT => "V" case BOOL => "Z" @@ -171,6 +165,9 @@ abstract class BTypes { assert(other.isRef, s"Cannot compute maxType: $this, $other") // Approximate `lub`. The common type of two references is always ObjectReference. ObjectReference + + case _: MethodBType => + throw new AssertionError(s"unexpected method type when computing maxType: $this") } /** @@ -568,22 +565,8 @@ abstract class BTypes { /** * A ClassBType represents a class or interface type. The necessary information to build a * ClassBType is extracted from compiler symbols and types, see BTypesFromSymbols. - * - * The `offset` and `length` fields are used to represent the internal name of the class. They - * are indices into some character array. The internal name can be obtained through the method - * `internalNameString`, which is abstract in this component. Name creation is assumed to be - * hash-consed, so if two ClassBTypes have the same internal name, they NEED to have the same - * `offset` and `length`. - * - * The actual implementation in subclass BTypesFromSymbols uses the global `chrs` array from the - * name table. This representation is efficient because the JVM class name is obtained through - * `classSymbol.javaBinaryName`. This already adds the necessary string to the `chrs` array, - * so it makes sense to reuse the same name table in the backend. - * - * ClassBType is not a case class because we want a custom equals method, and because the - * extractor extracts the internalName, which is what you typically need. */ - final class ClassBType(val offset: Int, val length: Int) extends RefBType { + final case class ClassBType(internalName: String) extends RefBType { /** * Write-once variable allows initializing a cyclic graph of infos. This is required for * nested classes. Example: for the definition `class A { class B }` we have @@ -630,12 +613,6 @@ abstract class BTypes { assert(info.memberClasses.forall(c => ifInit(c)(_.isNestedClass)), info.memberClasses) } - /** - * The internal name of a class is the string returned by java.lang.Class.getName, with all '.' - * replaced by '/'. For example "java/lang/String". - */ - def internalName: String = internalNameString(offset, length) - /** * @return The class name without the package prefix */ @@ -736,32 +713,9 @@ abstract class BTypes { } while (fcs == null) fcs } - - /** - * Custom equals / hashCode: we only compare the name (offset / length) - */ - override def equals(o: Any): Boolean = (this eq o.asInstanceOf[Object]) || (o match { - case c: ClassBType => c.offset == this.offset && c.length == this.length - case _ => false - }) - - override def hashCode: Int = { - import scala.runtime.Statics - var acc: Int = -889275714 - acc = Statics.mix(acc, offset) - acc = Statics.mix(acc, length) - Statics.finalizeHash(acc, 2) - } } object ClassBType { - /** - * Pattern matching on a ClassBType extracts the `internalName` of the class. - */ - def unapply(c: ClassBType): Option[String] = - if (c == null) None - else Some(c.internalName) - /** * Valid flags for InnerClass attribute entry. * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6 @@ -839,9 +793,9 @@ abstract class BTypes { * @param innerName The simple name of the inner class, may be null. * @param flags The flags for this class in the InnerClass entry. */ - case class InnerClassEntry(name: String, outerName: String, innerName: String, flags: Int) + final case class InnerClassEntry(name: String, outerName: String, innerName: String, flags: Int) - case class ArrayBType(componentType: BType) extends RefBType { + final case class ArrayBType(componentType: BType) extends RefBType { def dimension: Int = componentType match { case a: ArrayBType => 1 + a.dimension case _ => 1 @@ -853,7 +807,7 @@ abstract class BTypes { } } - case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType + final case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType /* Some definitions that are required for the implementation of BTypes. They are abstract because * initializing them requires information from types / symbols, which is not accessible here in diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index a0b8d28f1820..d02dd853d0a0 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -36,8 +36,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { coreBTypes.setBTypes(new CoreBTypes[this.type](this)) } - def internalNameString(offset: Int, length: Int) = new String(global.chrs, offset, length) - protected val classBTypeFromInternalNameMap = { global.perRunCaches.recordCache(collection.concurrent.TrieMap.empty[String, ClassBType]) } @@ -90,10 +88,10 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { s"Cannot create ClassBType for special class symbol ${classSym.fullName}") convertedClasses.getOrElse(classSym, { - val internalName = classSym.javaBinaryName.toTypeName + val internalName = classSym.javaBinaryName.toString // We first create and add the ClassBType to the hash map before computing its info. This // allows initializing cylic dependencies, see the comment on variable ClassBType._info. - val classBType = new ClassBType(internalName.start, internalName.length) + val classBType = ClassBType(internalName) convertedClasses(classSym) = classBType setClassInfo(classSym, classBType) }) From 45efefaa9b16532c1f61560bde09a7346dcfe5a1 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 16 Jan 2015 23:18:02 +0100 Subject: [PATCH 05/11] Type alias for InternalName --- .../scala/tools/nsc/backend/jvm/BTypes.scala | 19 +++++++++++++++---- .../nsc/backend/jvm/BTypesFromSymbols.scala | 3 ++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 879f2349187a..e1ca13cba989 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -10,7 +10,7 @@ import scala.tools.asm import asm.Opcodes /** - * The BTypes component defines The BType class hierarchy. BTypes encapsulates all type information + * The BTypes component defines The BType class hierarchy. BTypes encapsulate all type information * that is required after building the ASM nodes. This includes optimizations, generation of * InnerClass attributes and generation of stack map frames. * @@ -18,6 +18,8 @@ import asm.Opcodes * be queried by concurrent threads. */ abstract class BTypes { + import BTypes.InternalName + /** * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its * construction. @@ -29,12 +31,12 @@ abstract class BTypes { * Concurrent because stack map frames are computed when in the class writer, which might run * on multiple classes concurrently. */ - protected val classBTypeFromInternalNameMap: collection.concurrent.Map[String, ClassBType] + protected val classBTypeFromInternalNameMap: collection.concurrent.Map[InternalName, ClassBType] /** * Obtain a previously constructed ClassBType for a given internal name. */ - def classBTypeFromInternalName(internalName: String) = classBTypeFromInternalNameMap(internalName) + def classBTypeFromInternalName(internalName: InternalName) = classBTypeFromInternalNameMap(internalName) // Some core BTypes are required here, in class BType, where no Global instance is available. // The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual @@ -566,7 +568,7 @@ abstract class BTypes { * A ClassBType represents a class or interface type. The necessary information to build a * ClassBType is extracted from compiler symbols and types, see BTypesFromSymbols. */ - final case class ClassBType(internalName: String) extends RefBType { + final case class ClassBType(internalName: InternalName) extends RefBType { /** * Write-once variable allows initializing a cyclic graph of infos. This is required for * nested classes. Example: for the definition `class A { class B }` we have @@ -827,3 +829,12 @@ abstract class BTypes { */ def isCompilingPrimitive: Boolean } + +object BTypes { + /** + * A marker for strings that represent class internal names. + * Ideally the type would be incompatible with String, for example by making it a value class. + * But that would create overhead in a Collection[InternalName]. + */ + type InternalName = String +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index d02dd853d0a0..ef18d2984176 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -7,6 +7,7 @@ package scala.tools.nsc package backend.jvm import scala.tools.asm +import BTypes.InternalName /** * This class mainly contains the method classBTypeFromSymbol, which extracts the necessary @@ -37,7 +38,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } protected val classBTypeFromInternalNameMap = { - global.perRunCaches.recordCache(collection.concurrent.TrieMap.empty[String, ClassBType]) + global.perRunCaches.recordCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType]) } /** From ff9512812c6619d6ab5a1dde99c9569a3b27c813 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Thu, 11 Dec 2014 18:03:00 +0100 Subject: [PATCH 06/11] Remove an unnecessary hash map in BTypesFromSymbols There's already the map classBTypeFromInternalNameMap in BTypes which stores all ClassBTypes. --- .../nsc/backend/jvm/BTypesFromSymbols.scala | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index ef18d2984176..058208871068 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -41,11 +41,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { global.perRunCaches.recordCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType]) } - /** - * Cache for the method classBTypeFromSymbol. - */ - private val convertedClasses = perRunCaches.newMap[Symbol, ClassBType]() - // helpers that need access to global. // TODO @lry create a separate component, they don't belong to BTypesFromSymbols @@ -88,13 +83,11 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { (classSym != NothingClass && classSym != NullClass), s"Cannot create ClassBType for special class symbol ${classSym.fullName}") - convertedClasses.getOrElse(classSym, { - val internalName = classSym.javaBinaryName.toString - // We first create and add the ClassBType to the hash map before computing its info. This - // allows initializing cylic dependencies, see the comment on variable ClassBType._info. - val classBType = ClassBType(internalName) - convertedClasses(classSym) = classBType - setClassInfo(classSym, classBType) + val internalName = classSym.javaBinaryName.toString + classBTypeFromInternalNameMap.getOrElse(internalName, { + // The new ClassBType is added to the map in its constructor, before we set its info. This + // allows initializing cyclic dependencies, see the comment on variable ClassBType._info. + setClassInfo(classSym, ClassBType(internalName)) }) } From 7c1983d153a81ba43858a1bb5f0b59c5708bbfcf Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Thu, 11 Dec 2014 18:08:57 +0100 Subject: [PATCH 07/11] Cleanup asm-to-string debug helpers Introduces methods for textifying classes, methods, InsnLists and individual AbstractInsnNodes. --- .../tools/nsc/backend/jvm/AsmUtils.scala | 70 +++++++++++++++---- test/files/run/icode-reader-dead-code.check | 8 +++ 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala index 7269910af691..75aa0fc98469 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala @@ -5,10 +5,11 @@ package scala.tools.nsc.backend.jvm -import scala.tools.asm.tree.{AbstractInsnNode, ClassNode, MethodNode} -import java.io.PrintWriter +import scala.tools.asm.tree.{InsnList, AbstractInsnNode, ClassNode, MethodNode} +import java.io.{StringWriter, PrintWriter} import scala.tools.asm.util.{TraceClassVisitor, TraceMethodVisitor, Textifier} import scala.tools.asm.ClassReader +import scala.collection.convert.decorateAsScala._ object AsmUtils { @@ -36,19 +37,12 @@ object AsmUtils { def traceMethod(mnode: MethodNode): Unit = { println(s"Bytecode for method ${mnode.name}") - val p = new Textifier - val tracer = new TraceMethodVisitor(p) - mnode.accept(tracer) - val w = new PrintWriter(System.out) - p.print(w) - w.flush() + println(textify(mnode)) } def traceClass(cnode: ClassNode): Unit = { println(s"Bytecode for class ${cnode.name}") - val w = new PrintWriter(System.out) - cnode.accept(new TraceClassVisitor(w)) - w.flush() + println(textify(cnode)) } def traceClass(bytes: Array[Byte]): Unit = traceClass(readClass(bytes)) @@ -59,8 +53,56 @@ object AsmUtils { node } - def instructionString(instruction: AbstractInsnNode): String = instruction.getOpcode match { - case -1 => instruction.toString - case op => scala.tools.asm.util.Printer.OPCODES(op) + /** + * Returns a human-readable representation of the cnode ClassNode. + */ + def textify(cnode: ClassNode): String = { + val trace = new TraceClassVisitor(new PrintWriter(new StringWriter)) + cnode.accept(trace) + val sw = new StringWriter + val pw = new PrintWriter(sw) + trace.p.print(pw) + sw.toString } + + /** + * Returns a human-readable representation of the code in the mnode MethodNode. + */ + def textify(mnode: MethodNode): String = { + val trace = new TraceClassVisitor(new PrintWriter(new StringWriter)) + mnode.accept(trace) + val sw = new StringWriter + val pw = new PrintWriter(sw) + trace.p.print(pw) + sw.toString + } + + /** + * Returns a human-readable representation of the given instruction. + */ + def textify(insn: AbstractInsnNode): String = { + val trace = new TraceMethodVisitor(new Textifier) + insn.accept(trace) + val sw = new StringWriter + val pw = new PrintWriter(sw) + trace.p.print(pw) + sw.toString.trim + } + + /** + * Returns a human-readable representation of the given instruction sequence. + */ + def textify(insns: Iterator[AbstractInsnNode]): String = { + val trace = new TraceMethodVisitor(new Textifier) + insns.foreach(_.accept(trace)) + val sw: StringWriter = new StringWriter + val pw: PrintWriter = new PrintWriter(sw) + trace.p.print(pw) + sw.toString.trim + } + + /** + * Returns a human-readable representation of the given instruction sequence. + */ + def textify(insns: InsnList): String = textify(insns.iterator().asScala) } diff --git a/test/files/run/icode-reader-dead-code.check b/test/files/run/icode-reader-dead-code.check index d1739fed3b18..c9de93283e87 100644 --- a/test/files/run/icode-reader-dead-code.check +++ b/test/files/run/icode-reader-dead-code.check @@ -1,4 +1,7 @@ Bytecode for method f + + // access flags 0x11 + public final f()I L0 LINENUMBER 4 L0 ICONST_1 @@ -7,7 +10,11 @@ Bytecode for method f LOCALVARIABLE this Lp/A; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 + Bytecode for method f + + // access flags 0x11 + public final f()I L0 LINENUMBER 4 L0 ICONST_1 @@ -17,3 +24,4 @@ Bytecode for method f LOCALVARIABLE this Lp/A; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 + From afebceee78155c66564921eab7dd2170f820fdbf Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Tue, 16 Dec 2014 10:50:10 +0100 Subject: [PATCH 08/11] Construct ClassBTypes from parsed classfiles This infrastructure is required for the inliner: when inlining code from a classfile, the corresponding ClassBType is needed for various things (eg access checks, InnerClass attribute). The test creates two ClassBTypes for the same class: once using the (unpickled) Symbol, once using the parsed ASM ClassNode, and verifies that the two are the same. There's a cleanup to the InnerClass attribute: object T { class Member; def foo = { class Local } } class T For Java compatibility the InnerClass entry for Member says the class is nested in T (not in the module class T$). We now make sure to add that entry only to T, not to T$ (unless Member is actually referenced in the classfile T$, in that case it will be added, as required). --- .../tools/nsc/backend/jvm/BCodeHelpers.scala | 22 ++-- .../nsc/backend/jvm/BCodeSkelBuilder.scala | 2 +- .../scala/tools/nsc/backend/jvm/BTypes.scala | 116 ++++++++++++++---- .../nsc/backend/jvm/BTypesFromSymbols.scala | 110 +++++++++++------ .../scala/tools/nsc/backend/jvm/GenASM.scala | 20 ++- .../tools/nsc/backend/jvm/GenBCode.scala | 2 +- .../nsc/backend/jvm/opt/CodeRepository.scala | 93 ++++++++++++++ .../backend/jvm/opt/OptimizerReporting.scala | 21 ++++ test/files/jvm/innerClassAttribute/Test.scala | 29 +++-- test/files/jvm/t8582.check | 6 +- test/files/jvm/t8582.scala | 4 +- .../tools/nsc/backend/jvm/BTypesTest.scala | 2 +- .../jvm/opt/BTypesFromClassfileTest.scala | 95 ++++++++++++++ 13 files changed, 421 insertions(+), 101 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala create mode 100644 src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala create mode 100644 test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 806d4b277ce7..8d1c37532ece 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -791,32 +791,28 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { assert(moduleClass.companionClass == NoSymbol, moduleClass) innerClassBufferASM.clear() this.cunit = cunit - val moduleName = internalName(moduleClass) // + "$" - val mirrorName = moduleName.substring(0, moduleName.length() - 1) - val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL) + val bType = mirrorClassClassBType(moduleClass) val mirrorClass = new asm.tree.ClassNode mirrorClass.visit( classfileVersion, - flags, - mirrorName, + bType.info.flags, + bType.internalName, null /* no java-generic-signature */, ObjectReference.internalName, EMPTY_STRING_ARRAY ) - if (emitSource) { - mirrorClass.visitSource("" + cunit.source, - null /* SourceDebugExtension */) - } + if (emitSource) + mirrorClass.visitSource("" + cunit.source, null /* SourceDebugExtension */) - val ssa = getAnnotPickle(mirrorName, moduleClass.companionSymbol) + val ssa = getAnnotPickle(bType.internalName, moduleClass.companionSymbol) mirrorClass.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) emitAnnotations(mirrorClass, moduleClass.annotations ++ ssa) - addForwarders(isRemote(moduleClass), mirrorClass, mirrorName, moduleClass) + addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass) - innerClassBufferASM ++= classBTypeFromSymbol(moduleClass).info.memberClasses + innerClassBufferASM ++= bType.info.nestedClasses addInnerClassesASM(mirrorClass, innerClassBufferASM.toList) mirrorClass.visitEnd() @@ -932,7 +928,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments constructor.visitEnd() - innerClassBufferASM ++= classBTypeFromSymbol(cls).info.memberClasses + innerClassBufferASM ++= classBTypeFromSymbol(cls).info.nestedClasses addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList) beanInfoClass.visitEnd() diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 03bc32061b99..142c901c2166 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -118,7 +118,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { addClassFields() - innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.memberClasses + innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.nestedClasses gen(cd.impl) addInnerClassesASM(cnode, innerClassBufferASM.toList) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index e1ca13cba989..11ee0d127b2f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -8,6 +8,9 @@ package backend.jvm import scala.tools.asm import asm.Opcodes +import scala.tools.asm.tree.{InnerClassNode, ClassNode} +import opt.CodeRepository +import scala.collection.convert.decorateAsScala._ /** * The BTypes component defines The BType class hierarchy. BTypes encapsulate all type information @@ -20,6 +23,17 @@ import asm.Opcodes abstract class BTypes { import BTypes.InternalName + // Some core BTypes are required here, in class BType, where no Global instance is available. + // The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual + // implementation (CoreBTypesProxy) here because it has members that refer to global.Symbol. + val coreBTypes: CoreBTypesProxyGlobalIndependent[this.type] + import coreBTypes._ + + /** + * Tools for parsing classfiles, used by the inliner. + */ + val codeRepository: CodeRepository + /** * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its * construction. @@ -31,18 +45,77 @@ abstract class BTypes { * Concurrent because stack map frames are computed when in the class writer, which might run * on multiple classes concurrently. */ - protected val classBTypeFromInternalNameMap: collection.concurrent.Map[InternalName, ClassBType] + val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] /** - * Obtain a previously constructed ClassBType for a given internal name. + * Parse the classfile for `internalName` and construct the [[ClassBType]]. */ - def classBTypeFromInternalName(internalName: InternalName) = classBTypeFromInternalNameMap(internalName) + def classBTypeFromParsedClassfile(internalName: InternalName): ClassBType = { + classBTypeFromClassNode(codeRepository.classNode(internalName)) + } - // Some core BTypes are required here, in class BType, where no Global instance is available. - // The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual - // implementation (CoreBTypesProxy) here because it has members that refer to global.Symbol. - val coreBTypes: CoreBTypesProxyGlobalIndependent[this.type] - import coreBTypes._ + /** + * Construct the [[ClassBType]] for a parsed classfile. + */ + def classBTypeFromClassNode(classNode: ClassNode): ClassBType = { + classBTypeFromInternalName.getOrElse(classNode.name, { + setClassInfo(classNode, ClassBType(classNode.name)) + }) + } + + private def setClassInfo(classNode: ClassNode, classBType: ClassBType): ClassBType = { + val superClass = classNode.superName match { + case null => + assert(classNode.name == ObjectReference.internalName, s"class with missing super type: ${classNode.name}") + None + case superName => + Some(classBTypeFromParsedClassfile(superName)) + } + + val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) + + val flags = classNode.access + + /** + * Find all nested classes of classNode. The innerClasses attribute contains all nested classes + * that are declared inside classNode or used in the bytecode of classNode. So some of them are + * nested in some other class than classNode, and we need to filter them. + * + * For member classes, innerClassNode.outerName is defined, so we compare that to classNode.name. + * + * For local and anonymous classes, innerClassNode.outerName is null. Such classes are required + * to have an EnclosingMethod attribute declaring the outer class. So we keep those local and + * anonymous classes whose outerClass is classNode.name. + * + */ + def nestedInCurrentClass(innerClassNode: InnerClassNode): Boolean = { + (innerClassNode.outerName != null && innerClassNode.outerName == classNode.name) || + (innerClassNode.outerName == null && codeRepository.classNode(innerClassNode.name).outerClass == classNode.name) + } + + val nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({ + case i if nestedInCurrentClass(i) => classBTypeFromParsedClassfile(i.name) + })(collection.breakOut) + + // if classNode is a nested class, it has an innerClass attribute for itself. in this + // case we build the NestedInfo. + val nestedInfo = classNode.innerClasses.asScala.find(_.name == classNode.name) map { + case innerEntry => + val enclosingClass = + if (innerEntry.outerName != null) { + // if classNode is a member class, the outerName is non-null + classBTypeFromParsedClassfile(innerEntry.outerName) + } else { + // for anonymous or local classes, the outerName is null, but the enclosing class is + // stored in the EnclosingMethod attribute (which ASM encodes in classNode.outerClass). + classBTypeFromParsedClassfile(classNode.outerClass) + } + val staticFlag = (innerEntry.access & Opcodes.ACC_STATIC) != 0 + NestedInfo(enclosingClass, Option(innerEntry.outerName), Option(innerEntry.innerName), staticFlag) + } + classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo) + classBType + } /** * A BType is either a primitive type, a ClassBType, an ArrayBType of one of these, or a MethodType @@ -574,7 +647,7 @@ abstract class BTypes { * nested classes. Example: for the definition `class A { class B }` we have * * B.info.nestedInfo.outerClass == A - * A.info.memberClasses contains B + * A.info.nestedClasses contains B */ private var _info: ClassInfo = null @@ -589,7 +662,7 @@ abstract class BTypes { checkInfoConsistency() } - classBTypeFromInternalNameMap(internalName) = this + classBTypeFromInternalName(internalName) = this private def checkInfoConsistency(): Unit = { // we assert some properties. however, some of the linked ClassBType (members, superClass, @@ -597,7 +670,7 @@ abstract class BTypes { // best-effort verification. def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || p(c) - def isJLO(t: ClassBType) = t.internalName == "java/lang/Object" + def isJLO(t: ClassBType) = t.internalName == ObjectReference.internalName assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this") @@ -612,7 +685,7 @@ abstract class BTypes { s"Invalid interfaces in $this: ${info.interfaces}" ) - assert(info.memberClasses.forall(c => ifInit(c)(_.isNestedClass)), info.memberClasses) + assert(info.nestedClasses.forall(c => ifInit(c)(_.isNestedClass)), info.nestedClasses) } /** @@ -640,8 +713,9 @@ abstract class BTypes { outerName.orNull, innerName.orNull, GenBCode.mkFlags( - info.flags, - if (isStaticNestedClass) asm.Opcodes.ACC_STATIC else 0 + // the static flag in the InnerClass table has a special meaning, see InnerClass comment + info.flags & ~Opcodes.ACC_STATIC, + if (isStaticNestedClass) Opcodes.ACC_STATIC else 0 ) & ClassBType.INNER_CLASSES_FLAGS ) } @@ -757,12 +831,12 @@ abstract class BTypes { * through the superclass. * @param flags The java flags, obtained through `javaFlags`. Used also to derive * the flags for InnerClass entries. - * @param memberClasses Classes nested in this class. Those need to be added to the + * @param nestedClasses Classes nested in this class. Those need to be added to the * InnerClass table, see the InnerClass spec summary above. * @param nestedInfo If this describes a nested class, information for the InnerClass table. */ - case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int, - memberClasses: List[ClassBType], nestedInfo: Option[NestedInfo]) + final case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int, + nestedClasses: List[ClassBType], nestedInfo: Option[NestedInfo]) /** * Information required to add a class to an InnerClass table. @@ -779,10 +853,10 @@ abstract class BTypes { * a source-level property: if the class is in a static context (does not have an outer pointer). * This is checked when building the NestedInfo. */ - case class NestedInfo(enclosingClass: ClassBType, - outerName: Option[String], - innerName: Option[String], - isStaticNestedClass: Boolean) + final case class NestedInfo(enclosingClass: ClassBType, + outerName: Option[String], + innerName: Option[String], + isStaticNestedClass: Boolean) /** * This class holds the data for an entry in the InnerClass table. See the InnerClass summary diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 058208871068..9b8ac82340f8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -7,6 +7,7 @@ package scala.tools.nsc package backend.jvm import scala.tools.asm +import opt.CodeRepository import BTypes.InternalName /** @@ -33,11 +34,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val coreBTypes = new CoreBTypesProxy[this.type](this) import coreBTypes._ - final def intializeCoreBTypes(): Unit = { + val codeRepository = new CodeRepository(global.classPath) + + final def initializeCoreBTypes(): Unit = { coreBTypes.setBTypes(new CoreBTypes[this.type](this)) } - protected val classBTypeFromInternalNameMap = { + val classBTypeFromInternalName = { global.perRunCaches.recordCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType]) } @@ -84,7 +87,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { s"Cannot create ClassBType for special class symbol ${classSym.fullName}") val internalName = classSym.javaBinaryName.toString - classBTypeFromInternalNameMap.getOrElse(internalName, { + classBTypeFromInternalName.getOrElse(internalName, { // The new ClassBType is added to the map in its constructor, before we set its info. This // allows initializing cyclic dependencies, see the comment on variable ClassBType._info. setClassInfo(classSym, ClassBType(internalName)) @@ -118,25 +121,35 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { * code generation, but those duplicates will be eliminated when emitting the InnerClass * attribute. * - * Why doe we need to collect classes into innerClassBufferASM at all? To collect references to + * Why do we need to collect classes into innerClassBufferASM at all? To collect references to * nested classes, but NOT nested in C, that are used within C. */ val nestedClassSymbols = { // The lambdalift phase lifts all nested classes to the enclosing class, so if we collect // member classes right after lambdalift, we obtain all nested classes, including local and // anonymous ones. - val nestedClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(classSym)) + val nestedClasses = { + val nested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(classSym)) + if (isTopLevelModuleClass(classSym)) { + // For Java compatibility, member classes of top-level objects are treated as members of + // the top-level companion class, see comment below. + val members = exitingPickler(memberClassesOf(classSym)) + nested diff members + } else { + nested + } + } - // If this is a top-level class, and it has a companion object, the member classes of the - // companion are added as members of the class. For example: + // If this is a top-level class, the member classes of the companion object are added as + // members of the class. For example: // class C { } // object C { // class D // def f = { class E } // } - // The class D is added as a member of class C. The reason is that the InnerClass attribute - // for D will containt class "C" and NOT the module class "C$" as the outer class of D. - // This is done by buildNestedInfo, the reason is Java compatibility, see comment in BTypes. + // The class D is added as a member of class C. The reason is: for Java compatibility, the + // InnerClass attribute for D has "C" (NOT the module class "C$") as the outer class of D + // (done by buildNestedInfo). See comment in BTypes. // For consistency, the InnerClass entry for D needs to be present in C - to Java it looks // like D is a member of C, not C$. val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases @@ -166,53 +179,51 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } else true }) - val memberClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol) + val nestedClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol) val nestedInfo = buildNestedInfo(classSym) - classBType.info = ClassInfo(superClass, interfaces, flags, memberClasses, nestedInfo) + classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo) classBType } private def buildNestedInfo(innerClassSym: Symbol): Option[NestedInfo] = { assert(innerClassSym.isClass, s"Cannot build NestedInfo for non-class symbol $innerClassSym") - val isNested = !innerClassSym.rawowner.isPackageClass - if (!isNested) None + val isTopLevel = innerClassSym.rawowner.isPackageClass + if (isTopLevel) None else { // See comment in BTypes, when is a class marked static in the InnerClass table. val isStaticNestedClass = isOriginallyStaticOwner(innerClassSym.originalOwner) // After lambdalift (which is where we are), the rawowoner field contains the enclosing class. - val enclosingClassSym = { - if (innerClassSym.isJavaDefined && innerClassSym.rawowner.isModuleClass) { - // Example java source: class C { static class D { } } - // The Scala compiler creates a class and a module symbol for C. Because D is a static - // nested class, the symbol for D is nested in the module class C (not in the class C). - // For the InnerClass attribute, we use the class symbol C, which represents the situation - // in the source code. - - // Cannot use innerClassSym.isStatic: this method looks at the owner, which is a package - // at this pahse (after lambdalift, flatten). - assert(isOriginallyStaticOwner(innerClassSym.originalOwner), innerClassSym.originalOwner) - + val enclosingClass = { + // (1) Example java source: class C { static class D { } } + // The Scala compiler creates a class and a module symbol for C. Because D is a static + // nested class, the symbol for D is nested in the module class C (not in the class C). + // For the InnerClass attribute, we use the class symbol C, which represents the situation + // in the source code. + + // (2) Java compatibility. See the big comment in BTypes that summarizes the InnerClass spec. + if ((innerClassSym.isJavaDefined && innerClassSym.rawowner.isModuleClass) || // (1) + (!isAnonymousOrLocalClass(innerClassSym) && isTopLevelModuleClass(innerClassSym.rawowner))) { // (2) // phase travel for linkedCoC - does not always work in late phases - exitingPickler(innerClassSym.rawowner.linkedClassOfClass) + exitingPickler(innerClassSym.rawowner.linkedClassOfClass) match { + case NoSymbol => + // For top-level modules without a companion class, see doc of mirrorClassClassBType. + mirrorClassClassBType(exitingPickler(innerClassSym.rawowner)) + + case companionClass => + classBTypeFromSymbol(companionClass) + } + } else { + classBTypeFromSymbol(innerClassSym.rawowner) } - else innerClassSym.rawowner } - val enclosingClass: ClassBType = classBTypeFromSymbol(enclosingClassSym) val outerName: Option[String] = { - if (isAnonymousOrLocalClass(innerClassSym)) { - None - } else { - val outerName = innerClassSym.rawowner.javaBinaryName - // Java compatibility. See the big comment in BTypes that summarizes the InnerClass spec. - val outerNameModule = if (isTopLevelModuleClass(innerClassSym.rawowner)) outerName.dropModule - else outerName - Some(outerNameModule.toString) - } + if (isAnonymousOrLocalClass(innerClassSym)) None + else Some(enclosingClass.internalName) } val innerName: Option[String] = { @@ -224,6 +235,29 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } } + /** + * For top-level objects without a companion class, the compilere generates a mirror class with + * static forwarders (Java compat). There's no symbol for the mirror class, but we still need a + * ClassBType (its info.nestedClasses will hold the InnerClass entries, see comment in BTypes). + */ + def mirrorClassClassBType(moduleClassSym: Symbol): ClassBType = { + assert(isTopLevelModuleClass(moduleClassSym), s"not a top-level module class: $moduleClassSym") + val internalName = moduleClassSym.javaBinaryName.dropModule.toString + classBTypeFromInternalName.getOrElse(internalName, { + val c = ClassBType(internalName) + // class info consistent with BCodeHelpers.genMirrorClass + val nested = exitingPickler(memberClassesOf(moduleClassSym)) map classBTypeFromSymbol + c.info = ClassInfo( + superClass = Some(ObjectReference), + interfaces = Nil, + flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL, + nestedClasses = nested, + nestedInfo = None + ) + c + }) + } + /** * True for module classes of package level objects. The backend will generate a mirror class for * such objects. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index de468c0a6db2..abe3bc512c71 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -677,7 +677,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) } - def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor) { + def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor, isMirror: Boolean = false) { /* 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. @@ -698,11 +698,19 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => else innerSym.rawname + innerSym.moduleSuffix - // This collects all inner classes of csym, including local and anonymous: lambdalift makes - // them members of their enclosing class. - innerClassBuffer ++= exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(csym)) + innerClassBuffer ++= { + val members = exitingPickler(memberClassesOf(csym)) + // lambdalift makes all classes (also local, anonymous) members of their enclosing class + val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(csym)) - // Add members of the companion object (if top-level). why, see comment in BTypes.scala. + // for the mirror class, we take the members of the companion module class (Java compat, + // see doc in BTypes.scala). for module classes, we filter out those members. + if (isMirror) members + else if (isTopLevelModule(csym)) allNested diff members + else allNested + } + + // If this is a top-level class, add members of the companion object. val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases if (isTopLevelModule(linkedClass)) { // phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes, @@ -2796,7 +2804,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym) - addInnerClasses(modsym, mirrorClass) + addInnerClasses(modsym, mirrorClass, isMirror = true) mirrorClass.visitEnd() writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index a45f5866668f..d5e95c47cff6 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -286,7 +286,7 @@ abstract class GenBCode extends BCodeSyncAndTry { val initStart = Statistics.startTimer(BackendStats.bcodeInitTimer) arrivalPos = 0 // just in case scalaPrimitives.init() - bTypes.intializeCoreBTypes() + bTypes.initializeCoreBTypes() Statistics.stopTimer(BackendStats.bcodeInitTimer, initStart) // initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala new file mode 100644 index 000000000000..285855d08316 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala @@ -0,0 +1,93 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm +package opt + +import scala.tools.asm +import asm.tree._ +import scala.collection.convert.decorateAsScala._ +import scala.tools.nsc.io.AbstractFile +import scala.tools.nsc.util.ClassFileLookup +import OptimizerReporting._ + +class CodeRepository(val classPath: ClassFileLookup[AbstractFile]) { + import BTypes.InternalName + + /** + * Cache for parsed ClassNodes. + */ + val classes: collection.concurrent.Map[InternalName, ClassNode] = collection.concurrent.TrieMap.empty[InternalName, ClassNode] + + /** + * The class node for an internal name. If the class node is not yet available, it is parsed from + * the classfile on the compile classpath. + */ + def classNode(internalName: InternalName): ClassNode = { + classes.getOrElseUpdate(internalName, parseClass(internalName)) + } + + /** + * The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`. + * The declaration of the field may be in one of the superclasses. + * + * @return The [[FieldNode]] of the requested field and the [[InternalName]] of its declaring class. + */ + def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Option[(FieldNode, InternalName)] = { + val c = classNode(classInternalName) + c.fields.asScala.find(f => f.name == name && f.desc == descriptor).map((_, classInternalName)) orElse { + Option(c.superName).flatMap(n => fieldNode(n, name, descriptor)) + } + } + + /** + * The method node for a method matching `name` and `descriptor`, accessed in class `classInternalName`. + * The declaration of the method may be in one of the parents. + * + * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring class. + */ + def methodNode(classInternalName: InternalName, name: String, descriptor: String): Option[(MethodNode, InternalName)] = { + val c = classNode(classInternalName) + c.methods.asScala.find(m => m.name == name && m.desc == descriptor).map((_, classInternalName)) orElse { + val parents = Option(c.superName) ++ c.interfaces.asScala + // `view` to stop at the first result + parents.view.flatMap(methodNode(_, name, descriptor)).headOption + } + } + + private def parseClass(internalName: InternalName): ClassNode = { + val fullName = internalName.replace('/', '.') + classPath.findClassFile(fullName) map { classFile => + val classNode = new asm.tree.ClassNode() + val classReader = new asm.ClassReader(classFile.toByteArray) + // We don't need frames when inlining, but we want to keep the local variable table, so we + // don't use SKIP_DEBUG. + classReader.accept(classNode, asm.ClassReader.SKIP_FRAMES) + // SKIP_FRAMES leaves line number nodes. Remove them because they are not correct after + // inlining. + // TODO: we need to remove them also for classes that are not parsed from classfiles, why not simplify and do it once when inlining? + // OR: instead of skipping line numbers for inlined code, use write a SourceDebugExtension + // attribute that contains JSR-45 data that encodes debugging info. + // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.11 + // https://jcp.org/aboutJava/communityprocess/final/jsr045/index.html + removeLineNumberNodes(classNode) + classes(internalName) = classNode + classNode + } getOrElse { + inlineFailure(s"Class file for class $fullName not found.") + } + } + + private def removeLineNumberNodes(classNode: ClassNode): Unit = { + for (method <- classNode.methods.asScala) { + val iter = method.instructions.iterator() + while (iter.hasNext) iter.next() match { + case _: LineNumberNode => iter.remove() + case _ => + } + } + } +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala new file mode 100644 index 000000000000..c07fda2bc5d0 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala @@ -0,0 +1,21 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm + +import scala.tools.asm +import asm.tree._ + +object OptimizerReporting { + def methodSignature(className: String, methodName: String, methodDescriptor: String): String = { + className + "::" + methodName + methodDescriptor + } + + def methodSignature(className: String, method: MethodNode): String = methodSignature(className, method.name, method.desc) + + def inlineFailure(reason: String): Nothing = MissingRequirementError.signal(reason) + def assertionError(message: String): Nothing = throw new AssertionError(message) +} diff --git a/test/files/jvm/innerClassAttribute/Test.scala b/test/files/jvm/innerClassAttribute/Test.scala index 1c1a0522e43d..3820048cb411 100644 --- a/test/files/jvm/innerClassAttribute/Test.scala +++ b/test/files/jvm/innerClassAttribute/Test.scala @@ -84,17 +84,15 @@ object Test extends BytecodeTest { } def testA3() = { - def t(c: String) = { - val List(b1, b2) = innerClassNodes(c) - // the outer class for classes nested inside top-level modules is not the module class, but the mirror class. - // this is a hack for java interop, handled in the backend. see BTypes.scala, comment on "Java Compatibility". - assertMember(b1, "A3", "B1", flags = publicStatic) - assertMember(b2, "A3", "B2$", flags = publicStatic) - } - t("A3$") - // the mirror class has the same inner class attributes as the module - // class (added when the mirror is created in the backend) - t("A3") + // the inner class entries for top-level object members are in the companion class, so nothing in the module class + val List() = innerClassNodes("A3$") + + // inner class entries in the companion class (a backend-generated mirror class in this case) + val List(b1, b2) = innerClassNodes("A3") + // the outer class for classes nested inside top-level modules is not the module class, but the mirror class. + // this is a hack for java interop, handled in the backend. see BTypes.scala, comment on "Java Compatibility". + assertMember(b1, "A3", "B1", flags = publicStatic) + assertMember(b2, "A3", "B2$", flags = publicStatic) } def testA4() = { @@ -164,7 +162,10 @@ object Test extends BytecodeTest { } def testA15() = { - val List(b) = innerClassNodes("A15") + // no member classes, only anonymous / local. these are nested in the module class, not the companion. + val List() = innerClassNodes("A15") + + val List(b) = innerClassNodes("A15$") assertLocal(b, "A15$B$3", "B$3") val List(_, c) = innerClassNodes("A15$B$3") @@ -283,9 +284,7 @@ object Test extends BytecodeTest { assertMember(i3c, "A21", "I3$", flags = publicStatic) assertLocal(j1, "A21$J1$1", "J1$1") - val List(i2m, i3m, j3, j4, j5) = innerClassNodes("A21$") - assertMember(i2m, "A21", "I2", flags = publicStatic) - assertMember(i3m, "A21", "I3$", flags = publicStatic) + val List(j3, j4, j5) = innerClassNodes("A21$") assertLocal(j3, "A21$J3$1", "J3$1") assertLocal(j4, "A21$J4$1", "J4$1") assertLocal(j5, "A21$J5$1", "J5$1") // non-static! diff --git a/test/files/jvm/t8582.check b/test/files/jvm/t8582.check index 564f482ff87f..e38836627081 100644 --- a/test/files/jvm/t8582.check +++ b/test/files/jvm/t8582.check @@ -14,10 +14,10 @@ Reflection can find direct nested classes (A2-B2-C2) A2$B2: List(class A2$B2$C2) A2$B2$C2: List() -Mirror classes have the same InnerClass attributes as the corresponding module class: - className[p1/p2/Singleton$Singleton$] outerClassName[p1/p2/Singleton] innerName[Singleton$] access[9] -Module class +The InnerClass attribute of a mirror class contains the members of the module class: className[p1/p2/Singleton$Singleton$] outerClassName[p1/p2/Singleton] innerName[Singleton$] access[9] +The module members are not in the InnerClass table of the module class (unless referenced): + An outer class has a InnerClass attribute for direct nested classes className[A1$B1] outerClassName[A1] innerName[B1] access[1] diff --git a/test/files/jvm/t8582.scala b/test/files/jvm/t8582.scala index 8a57ef795239..e9a01f901664 100644 --- a/test/files/jvm/t8582.scala +++ b/test/files/jvm/t8582.scala @@ -55,9 +55,9 @@ object Test extends BytecodeTest { println(nested(classOf[A2#B2])) println(nested(classOf[A2#B2#C2])) - nprintln("Mirror classes have the same InnerClass attributes as the corresponding module class:") + nprintln("The InnerClass attribute of a mirror class contains the members of the module class:") printInner("p1.p2.Singleton") // mirror class - println("Module class") + println("The module members are not in the InnerClass table of the module class (unless referenced):") printInner("p1.p2.Singleton$") nprintln("An outer class has a InnerClass attribute for direct nested classes") diff --git a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala index 221aad653679..2347e8288ea8 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala @@ -19,7 +19,7 @@ class BTypesTest { val btypes = new BTypesFromSymbols[g.type](g) import btypes._ - duringBackend(btypes.intializeCoreBTypes()) + duringBackend(btypes.initializeCoreBTypes()) def classBTypeFromSymbol(sym: Symbol) = duringBackend(btypes.classBTypeFromSymbol(sym)) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala new file mode 100644 index 000000000000..2975bd060dba --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala @@ -0,0 +1,95 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.tools.asm.Opcodes._ +import org.junit.Assert._ + +import scala.tools.nsc.backend.jvm.BTypes.InternalName +import scala.tools.testing.AssertUtil._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ + +import scala.collection.convert.decorateAsScala._ + +@RunWith(classOf[JUnit4]) +class BTypesFromClassfileTest { + val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode") + + import compiler._ + import definitions._ + import genBCode.bTypes + import bTypes._ + + def duringBackend[T](f: => T) = compiler.exitingDelambdafy(f) + + val run = new compiler.Run() // initializes some of the compiler + duringBackend(bTypes.initializeCoreBTypes()) + + def clearCache() = bTypes.classBTypeFromInternalName.clear() + + def sameBType(fromSym: ClassBType, fromClassfile: ClassBType, checked: Set[InternalName] = Set.empty): Set[InternalName] = { + if (checked(fromSym.internalName)) checked + else { + assert(fromSym == fromClassfile, s"$fromSym != $fromClassfile") + sameInfo(fromSym.info, fromClassfile.info, checked + fromSym.internalName) + } + } + + def sameBTypes(fromSyms: Iterable[ClassBType], fromClassfiles: Iterable[ClassBType], checked: Set[InternalName]): Set[InternalName] = { + assert(fromSyms.size == fromClassfiles.size, s"\n$fromSyms\n$fromClassfiles") + (fromSyms, fromClassfiles).zipped.foldLeft(checked) { + case (chk, (fromSym, fromClassfile)) => sameBType(fromSym, fromClassfile, chk) + } + } + + def sameInfo(fromSym: ClassInfo, fromClassfile: ClassInfo, checked: Set[InternalName]): Set[InternalName] = { + assert({ + // Nested class symbols can undergo makeNotPrivate (ExplicitOuter). But this is only applied + // for symbols of class symbols that are being compiled, not those read from a pickle. + // So a class may be public in bytecode, but the symbol still says private. + if (fromSym.nestedInfo.isEmpty) fromSym.flags == fromClassfile.flags + else (fromSym.flags | ACC_PRIVATE | ACC_PUBLIC) == (fromClassfile.flags | ACC_PRIVATE | ACC_PUBLIC) + }, s"class flags differ\n$fromSym\n$fromClassfile") + + val chk1 = sameBTypes(fromSym.superClass, fromClassfile.superClass, checked) + + val chk2 = sameBTypes(fromSym.interfaces, fromClassfile.interfaces, chk1) + + // The fromSym info has only member classes, no local or anonymous. The symbol is read from the + // Scala pickle data and only member classes are created / entered. + // (This is different for symbols that are being compiled, there flatten will enter all local + // and anonymous classes as members of the outer class. But not for unpickled symbols). + // The fromClassfile info has all nested classes, including anonymous and local. So we filter + // them out: member classes are identified by having the `outerName` defined. + val memberClassesFromClassfile = fromClassfile.nestedClasses.filter(_.info.nestedInfo.get.outerName.isDefined) + // Sorting is required: the backend sorts all InnerClass entries by internalName before writing + // them to the classfile (to make it deterministic: the entries are collected in a Set during + // code generation). + val chk3 = sameBTypes(fromSym.nestedClasses.sortBy(_.internalName), memberClassesFromClassfile.sortBy(_.internalName), chk2) + sameBTypes(fromSym.nestedInfo.map(_.enclosingClass), fromClassfile.nestedInfo.map(_.enclosingClass), chk3) + } + + def check(classSym: Symbol): Unit = duringBackend { + clearCache() + val fromSymbol = classBTypeFromSymbol(classSym) + clearCache() + val fromClassfile = bTypes.classBTypeFromParsedClassfile(fromSymbol.internalName) + sameBType(fromSymbol, fromClassfile) + } + + @Test + def compareClassBTypes(): Unit = { + // Note that not only these classes are tested, but also all their parents and all nested + // classes in their InnerClass attributes. + check(ObjectClass) + check(JavaNumberClass) + check(ConsClass) + check(ListModule.moduleClass) + } +} From f3f9eb41aacc9731f8f00ed6c2bd52a30eb4ee76 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 16 Jan 2015 22:36:49 +0100 Subject: [PATCH 09/11] Address review feedback - Rename CodeRepository to ByteCodeRepository - Scaladoc on OptimizerReporting - Scaladoc on ByteCodeRepository --- .../scala/tools/nsc/backend/jvm/BTypes.scala | 13 ++++--- .../nsc/backend/jvm/BTypesFromSymbols.scala | 10 ++--- ...ository.scala => ByteCodeRepository.scala} | 37 ++++++++++++++----- .../backend/jvm/opt/OptimizerReporting.scala | 3 ++ 4 files changed, 44 insertions(+), 19 deletions(-) rename src/compiler/scala/tools/nsc/backend/jvm/opt/{CodeRepository.scala => ByteCodeRepository.scala} (70%) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 11ee0d127b2f..a9bce82acd63 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -9,7 +9,7 @@ package backend.jvm import scala.tools.asm import asm.Opcodes import scala.tools.asm.tree.{InnerClassNode, ClassNode} -import opt.CodeRepository +import opt.ByteCodeRepository import scala.collection.convert.decorateAsScala._ /** @@ -32,7 +32,10 @@ abstract class BTypes { /** * Tools for parsing classfiles, used by the inliner. */ - val codeRepository: CodeRepository + val byteCodeRepository: ByteCodeRepository + + // Allows to define per-run caches here and in the CallGraph component, which don't have a global + def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T /** * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its @@ -45,13 +48,13 @@ abstract class BTypes { * Concurrent because stack map frames are computed when in the class writer, which might run * on multiple classes concurrently. */ - val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] + val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType]) /** * Parse the classfile for `internalName` and construct the [[ClassBType]]. */ def classBTypeFromParsedClassfile(internalName: InternalName): ClassBType = { - classBTypeFromClassNode(codeRepository.classNode(internalName)) + classBTypeFromClassNode(byteCodeRepository.classNode(internalName)) } /** @@ -90,7 +93,7 @@ abstract class BTypes { */ def nestedInCurrentClass(innerClassNode: InnerClassNode): Boolean = { (innerClassNode.outerName != null && innerClassNode.outerName == classNode.name) || - (innerClassNode.outerName == null && codeRepository.classNode(innerClassNode.name).outerClass == classNode.name) + (innerClassNode.outerName == null && byteCodeRepository.classNode(innerClassNode.name).outerClass == classNode.name) } val nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({ diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 9b8ac82340f8..94f9b585d96d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -7,7 +7,9 @@ package scala.tools.nsc package backend.jvm import scala.tools.asm -import opt.CodeRepository +import opt.ByteCodeRepository +import scala.tools.asm.tree.ClassNode +import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository.Source import BTypes.InternalName /** @@ -34,15 +36,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val coreBTypes = new CoreBTypesProxy[this.type](this) import coreBTypes._ - val codeRepository = new CodeRepository(global.classPath) + val byteCodeRepository = new ByteCodeRepository(global.classPath, recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, (ClassNode, Source)])) final def initializeCoreBTypes(): Unit = { coreBTypes.setBTypes(new CoreBTypes[this.type](this)) } - val classBTypeFromInternalName = { - global.perRunCaches.recordCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType]) - } + def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T = perRunCaches.recordCache(cache) // helpers that need access to global. // TODO @lry create a separate component, they don't belong to BTypesFromSymbols diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala similarity index 70% rename from src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala rename to src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala index 285855d08316..7b424d2107b3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala @@ -13,22 +13,32 @@ import scala.collection.convert.decorateAsScala._ import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.util.ClassFileLookup import OptimizerReporting._ +import ByteCodeRepository._ +import BTypes.InternalName -class CodeRepository(val classPath: ClassFileLookup[AbstractFile]) { - import BTypes.InternalName - +/** + * The ByteCodeRepository provides utilities to read the bytecode of classfiles from the compilation + * classpath. Parsed classes are cached in the `classes` map. + * + * @param classPath The compiler classpath where classfiles are searched and read from. + * @param classes Cache for parsed ClassNodes. Also stores the source of the bytecode: + * [[Classfile]] if read from `classPath`, [[CompilationUnit]] if the bytecode + * corresponds to a class being compiled. + */ +class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val classes: collection.concurrent.Map[InternalName, (ClassNode, Source)]) { /** - * Cache for parsed ClassNodes. + * The class node and source for an internal name. If the class node is not yet available, it is + * parsed from the classfile on the compile classpath. */ - val classes: collection.concurrent.Map[InternalName, ClassNode] = collection.concurrent.TrieMap.empty[InternalName, ClassNode] + def classNodeAndSource(internalName: InternalName): (ClassNode, Source) = { + classes.getOrElseUpdate(internalName, (parseClass(internalName), Classfile)) + } /** * The class node for an internal name. If the class node is not yet available, it is parsed from * the classfile on the compile classpath. */ - def classNode(internalName: InternalName): ClassNode = { - classes.getOrElseUpdate(internalName, parseClass(internalName)) - } + def classNode(internalName: InternalName) = classNodeAndSource(internalName)._1 /** * The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`. @@ -74,7 +84,6 @@ class CodeRepository(val classPath: ClassFileLookup[AbstractFile]) { // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.11 // https://jcp.org/aboutJava/communityprocess/final/jsr045/index.html removeLineNumberNodes(classNode) - classes(internalName) = classNode classNode } getOrElse { inlineFailure(s"Class file for class $fullName not found.") @@ -91,3 +100,13 @@ class CodeRepository(val classPath: ClassFileLookup[AbstractFile]) { } } } + +object ByteCodeRepository { + /** + * The source of a ClassNode in the ByteCodeRepository. Can be either [[CompilationUnit]] if the + * class is being compiled or [[Classfile]] if the class was parsed from the compilation classpath. + */ + sealed trait Source + object CompilationUnit extends Source + object Classfile extends Source +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala index c07fda2bc5d0..7002e43d98f1 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala @@ -9,6 +9,9 @@ package backend.jvm import scala.tools.asm import asm.tree._ +/** + * Reporting utilities used in the optimizer. + */ object OptimizerReporting { def methodSignature(className: String, methodName: String, methodDescriptor: String): String = { className + "::" + methodName + methodDescriptor From db524380baa7f26743d49cbeca78ab1542cae97e Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 9 Jan 2015 15:58:47 -0800 Subject: [PATCH 10/11] New script layout --- scripts/jobs/{scala-release-2.11.x-build => integrate/bootstrap} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/jobs/{scala-release-2.11.x-build => integrate/bootstrap} (100%) diff --git a/scripts/jobs/scala-release-2.11.x-build b/scripts/jobs/integrate/bootstrap similarity index 100% rename from scripts/jobs/scala-release-2.11.x-build rename to scripts/jobs/integrate/bootstrap From 98714011fcd0cd5f91e071540daff190d0d16795 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 9 Jan 2015 18:34:07 -0800 Subject: [PATCH 11/11] WIP: New PR validation --- scripts/common | 153 +++++++++++++++++++++++++++++ scripts/jobs/integrate/bootstrap | 74 +------------- scripts/jobs/validate/publish-core | 44 +++++++++ scripts/jobs/validate/test | 17 ++++ 4 files changed, 217 insertions(+), 71 deletions(-) create mode 100644 scripts/common create mode 100755 scripts/jobs/validate/publish-core create mode 100755 scripts/jobs/validate/test diff --git a/scripts/common b/scripts/common new file mode 100644 index 000000000000..b07546937946 --- /dev/null +++ b/scripts/common @@ -0,0 +1,153 @@ +# This is for forcibly stopping the job from a subshell (see test +# below). +trap "exit 1" TERM +export TOP_PID=$$ +set -e + +# Known problems : does not fare well with interrupted, partial +# compilations. We should perhaps have a multi-dependency version +# of do_i_have below + +LOGGINGDIR="$WORKSPACE/logs" +mkdir -p $LOGGINGDIR + +unset SBT_HOME +SBT_HOME="$WORKSPACE/.sbt" +mkdir -p $SBT_HOME +IVY_CACHE="$WORKSPACE/.ivy2" +mkdir -p $IVY_CACHE +rm -rf $IVY_CACHE/cache/org.scala-lang + +# temp dir where all 'non-build' operation are performed +TMP_ROOT_DIR=$(mktemp -d -t pr-scala.XXXX) +TMP_DIR="${TMP_ROOT_DIR}/tmp" +mkdir "${TMP_DIR}" + + +# detect sed version and how to enable extended regexes +SEDARGS="-n$(if (echo "a" | sed -nE "s/a/b/" &> /dev/null); then echo E; else echo r; fi)" + + + +# :docstring test: +# Usage: test +# Executes , logging the launch of the command to the +# main log file, and kills global script execution with the TERM +# signal if the commands ends up failing. +# DO NOT USE ON FUNCTIONS THAT DECLARE VARIABLES, +# AS YOU'LL BE RUNNING IN A SUBSHELL AND VARIABLE DECLARATIONS WILL BE LOST +# :end docstring: + +function test() { + echo "### $@" + "$@" + status=$? + if [ $status -ne 0 ]; then + say "### ERROR with $1" + kill -s TERM $TOP_PID + fi +} + +# :docstring say: +# Usage: say +# Prints to both console and the main log file. +# :end docstring: + +function say(){ + (echo "$@") | tee -a $LOGGINGDIR/compilation-$SCALADATE-$SCALAHASH.log +} + +# General debug logging +# $* - message +function debug () { + echo "----- $*" +} + +function parseScalaProperties(){ + propFile="$baseDir/$1" + if [ ! -f $propFile ]; then + echo "Property file $propFile not found." + exit 1 + else + awk -f "$scriptsDir/readproperties.awk" "$propFile" > "$propFile.sh" + . "$propFile.sh" # yeah yeah, not that secure, improvements welcome (I tried, but bash made me cry again) + fi +} + + +## TAKEN FROM UBER-BUILD, except that it "returns" (via $RES) true/false +# Check if an artifact is available +# $1 - groupId +# $2 - artifacId +# $3 - version +# $4 - extra repository to look in (optional) +# return value in $RES +function checkAvailability () { + pushd "${TMP_DIR}" + rm -rf * + +# pom file for the test project + cat > pom.xml << EOF + + 4.0.0 + com.typesafe + typesafeDummy + war + 1.0-SNAPSHOT + Dummy + http://127.0.0.1 + + + $1 + $2 + $3 + + + + + sonatype.snapshot + Sonatype maven snapshot repository + https://oss.sonatype.org/content/repositories/snapshots + + daily + + +EOF + + if [ -n "$4" ] + then +# adds the extra repository + cat >> pom.xml << EOF + + extrarepo + extra repository + $4 + +EOF + fi + + cat >> pom.xml << EOF + + +EOF + + set +e + mvn "${MAVEN_ARGS[@]}" compile &> "${TMP_DIR}/mvn.log" + RES=$? + # Quiet the maven, but allow diagnosing problems. + grep -i downloading "${TMP_DIR}/mvn.log" + grep -i exception "${TMP_DIR}/mvn.log" + grep -i error "${TMP_DIR}/mvn.log" + set -e + +# log the result + if [ ${RES} == 0 ] + then + debug "$1:$2:jar:$3 found !" + RES=true + else + debug "$1:$2:jar:$3 not found !" + RES=false + fi + popd +} diff --git a/scripts/jobs/integrate/bootstrap b/scripts/jobs/integrate/bootstrap index 09a97dc34bfb..46d610018c39 100755 --- a/scripts/jobs/integrate/bootstrap +++ b/scripts/jobs/integrate/bootstrap @@ -71,7 +71,6 @@ # set to something besides the default to build nightly snapshots of the modules instead of some tagged version moduleVersioning=${moduleVersioning-"versions.properties"} -baseDir=${WORKSPACE-`pwd`} publishPrivateTask=${publishPrivateTask-"publish"} publishSonatypeTaskCore=${publishSonatypeTaskCore-"publish-signed"} publishSonatypeTaskModules=${publishSonatypeTaskModules-"publish-signed"} @@ -93,67 +92,11 @@ forceRebuild=${forceRebuild-no} antBuildTask="${antBuildTask-nightly}" # TESTING leave empty to avoid the sanity check (don't set it to "init" because ant will croak) clean="clean" # TESTING leave empty to speed up testing -scriptsDir="$WORKSPACE/scripts" - -# This is for forcibly stopping the job from a subshell (see test -# below). -trap "exit 1" TERM -export TOP_PID=$$ -set -e - -# Known problems : does not fare well with interrupted, partial -# compilations. We should perhaps have a multi-dependency version -# of do_i_have below - -LOGGINGDIR="$WORKSPACE/logs" -mkdir -p $LOGGINGDIR - -unset SBT_HOME -SBT_HOME="$WORKSPACE/.sbt" -mkdir -p $SBT_HOME -IVY_CACHE="$WORKSPACE/.ivy2" -mkdir -p $IVY_CACHE -rm -rf $IVY_CACHE/cache/org.scala-lang - -# temp dir where all 'non-build' operation are performed -TMP_ROOT_DIR=$(mktemp -d -t pr-scala.XXXX) -TMP_DIR="${TMP_ROOT_DIR}/tmp" -mkdir "${TMP_DIR}" -# detect sed version and how to enable extended regexes -SEDARGS="-n$(if (echo "a" | sed -nE "s/a/b/" &> /dev/null); then echo E; else echo r; fi)" - - - -# :docstring test: -# Usage: test -# Executes , logging the launch of the command to the -# main log file, and kills global script execution with the TERM -# signal if the commands ends up failing. -# DO NOT USE ON FUNCTIONS THAT DECLARE VARIABLES, -# AS YOU'LL BE RUNNING IN A SUBSHELL AND VARIABLE DECLARATIONS WILL BE LOST -# :end docstring: - -function test() { - echo "### $@" - "$@" - status=$? - if [ $status -ne 0 ]; then - say "### ERROR with $1" - kill -s TERM $TOP_PID - fi -} - -# :docstring say: -# Usage: say -# Prints to both console and the main log file. -# :end docstring: - -function say(){ - (echo "$@") | tee -a $LOGGINGDIR/compilation-$SCALADATE-$SCALAHASH.log -} - +baseDir=${WORKSPACE-`pwd`} +scriptsDir="$baseDir/scripts" +. $scriptsDir/common # we must change ivy home to get a fresh ivy cache, otherwise we get half-bootstrapped scala # rm it in case it existed (and there's no ivy2-shadow, which indicates we're running in a TESTING environment)... @@ -168,17 +111,6 @@ mkdir -p $baseDir/resolutionScratch_ privateCred="private-repo" privateRepo="http://private-repo.typesafe.com/typesafe/scala-release-temp/" -function parseScalaProperties(){ - propFile="$baseDir/$1" - if [ ! -f $propFile ]; then - echo "Property file $propFile not found." - exit 1 - else - awk -f "$scriptsDir/readproperties.awk" "$propFile" > "$propFile.sh" - . "$propFile.sh" # yeah yeah, not that secure, improvements welcome (I tried, but bash made me cry again) - fi -} - ##### git gfxd() { git clean -fxd # TESTING diff --git a/scripts/jobs/validate/publish-core b/scripts/jobs/validate/publish-core new file mode 100755 index 000000000000..9dff5a34b0d9 --- /dev/null +++ b/scripts/jobs/validate/publish-core @@ -0,0 +1,44 @@ +#!/bin/bash -e +# This script publishes the core of Scala to maven for use as locker downstream, +# and saves the relevant properties used in its build artifacts, versions.properties. +# (This means we'll use locker instead of quick downstream in dbuild. +# The only downside is that backend improvements don't improve compiler performance itself until they are in STARR). +# The version is suffixed with "-${sha:0:7}-SNAPSHOT" + +baseDir=${WORKSPACE-`pwd`} +scriptsDir="$baseDir/scripts" +. $scriptsDir/common + +case $prDryRun in + yep) + echo "DRY RUN" + mkdir -p build/pack ; mkdir -p dists/maven/latest + ;; + *) + sha=$(git rev-parse HEAD) # TODO: warn if $repo_ref != $sha (we shouldn't do PR validation using symbolic gitrefs) + echo "sha/repo_ref == $sha/$repo_ref ?" + + parseScalaProperties build.number + + ./pull-binary-libs.sh + # "noyoudont" is there juuuust in case + antDeployArgs="-Dmaven.version.suffix=\"-${sha:0:7}-SNAPSHOT\" -Dremote.snapshot.repository=$prRepoUrl -Drepository.credentials.id=pr-scala -Dremote.release.repository=noyoudont" + + echo ">>> Getting Scala version number." + ant -q $antDeployArgs init + parseScalaProperties buildcharacter.properties # produce maven_version_number + + echo ">>> Checking availability of Scala ${maven_version_number} in $prRepoUrl." + checkAvailability "org.scala-lang" "scala-library" "${maven_version_number}" $prRepoUrl; libraryAvailable=$RES + checkAvailability "org.scala-lang" "scala-reflect" "${maven_version_number}" $prRepoUrl; reflectAvailable=$RES + checkAvailability "org.scala-lang" "scala-compiler" "${maven_version_number}" $prRepoUrl; compilerAvailable=$RES + + if $libraryAvailable && $reflectAvailable && $compilerAvailable; then + echo "Scala core already built!" + else + ant $antDeployArgs $antBuildArgs publish-opt-nodocs + fi + + mv buildcharacter.properties jenkins.properties # parsed by the jenkins job + ;; +esac diff --git a/scripts/jobs/validate/test b/scripts/jobs/validate/test new file mode 100755 index 000000000000..47b95bdae57f --- /dev/null +++ b/scripts/jobs/validate/test @@ -0,0 +1,17 @@ +#!/bin/bash -e + +case $prDryRun in + yep) + echo "DRY RUN" + ;; + *) + ./pull-binary-libs.sh + + # build quick using STARR built upstream, as specified by maven_version_number + # (in that sense it's locker, since it was built with starr by that upstream job) + ant -Dstarr.version=$maven_version_number \ + -Dscalac.args.optimise=-optimise \ + -Dlocker.skip=1 -Dstarr.use.released=1 -Dextra.repo.url=$prRepoUrl \ + $testExtraArgs ${testTarget-test.core docs.done} + ;; +esac \ No newline at end of file