Skip to content

Another take on avoiding garbage in classpath lookups #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
10 changes: 5 additions & 5 deletions src/compiler/scala/reflect/quasiquotes/Reifiers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ trait Reifiers { self: Quasiquotes =>
* and ends with reified tree:
*
* {
* val name$1: universe.TermName = universe.build.freshTermName(prefix1)
* val name\$1: universe.TermName = universe.build.freshTermName(prefix1)
* ...
* val name$N: universe.TermName = universe.build.freshTermName(prefixN)
* val name\$N: universe.TermName = universe.build.freshTermName(prefixN)
* tree
* }
*
Expand Down Expand Up @@ -348,9 +348,9 @@ trait Reifiers { self: Quasiquotes =>
*
* Sample execution of previous concrete list reifier:
*
* > val lst = List(foo, bar, qq$f3948f9s$1)
* > val lst = List(foo, bar, qq\$f3948f9s\$1)
* > reifyHighRankList(lst) { ... } { ... }
* q"List($foo, $bar) ++ ${holeMap(qq$f3948f9s$1).tree}"
* q"List(\$foo, \$bar) ++ \${holeMap(qq\$f3948f9s\$1).tree}"
*/
def reifyHighRankList(xs: List[Any])(fill: PartialFunction[Any, Tree])(fallback: Any => Tree): Tree

Expand All @@ -373,7 +373,7 @@ trait Reifiers { self: Quasiquotes =>
case List(Placeholder(Hole(tree, DotDotDot))) => tree
}

/** Reifies arbitrary list filling ..$x and ...$y holeMap when they are put
/** Reifies arbitrary list filling ..\$x and ...\$y holeMap when they are put
* in the correct position. Falls back to regular reification for zero rank
* elements.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/ScriptRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import util.Exceptional.unwrap
* For example, here is a complete Scala script on Unix:
* {{{
* #!/bin/sh
* exec scala "$0" "$@"
* exec scala "\$0" "\$@"
* !#
* Console.println("Hello, world!")
* args.toList foreach Console.println
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/ast/TreeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
*
* x.asInstanceOf[`pt`] up to phase uncurry
* x.asInstanceOf[`pt`]() if after uncurry but before erasure
* x.$asInstanceOf[`pt`]() if at or after erasure
* x.\$asInstanceOf[`pt`]() if at or after erasure
*/
override def mkCast(tree: Tree, pt: Type): Tree = {
debuglog("casting " + tree + ":" + tree.tpe + " to " + pt + " at phase: " + phase)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
asm.Opcodes.PUTSTATIC,
thisBType.internalName,
strMODULE_INSTANCE_FIELD,
thisBType.descriptor
thisBTypeDescriptor
)
}
}
Expand Down Expand Up @@ -1092,7 +1092,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
if (style == Super) {
if (receiverClass.isTrait && !method.isJavaDefined) {
val staticDesc = MethodBType(typeToBType(method.owner.info) :: bmType.argumentTypes, bmType.returnType).descriptor
val staticName = traitSuperAccessorName(method).toString
val staticName = traitSuperAccessorName(method)
bc.invokestatic(receiverName, staticName, staticDesc, isInterface, pos)
} else {
if (receiverClass.isTraitOrInterface) {
Expand Down
6 changes: 3 additions & 3 deletions src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ abstract class BCodeHelpers extends BCodeIdiomatic {

def needsStaticImplMethod(sym: Symbol) = sym.hasAttachment[global.mixer.NeedStaticImpl.type]

final def traitSuperAccessorName(sym: Symbol): Name = {
final def traitSuperAccessorName(sym: Symbol): String = {
val name = sym.javaSimpleName
if (sym.isMixinConstructor) name
else name.append(nme.NAME_JOIN_STRING)
if (sym.isMixinConstructor) name.toString
else name + nme.NAME_JOIN_STRING
}

/**
Expand Down
22 changes: 12 additions & 10 deletions src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
final val MaximumJvmParameters = 254

// current class
var cnode: asm.tree.ClassNode = null
var thisBType: ClassBType = null
var cnode: asm.tree.ClassNode = null
var thisBType: ClassBType = null
var thisBTypeDescriptor: String = null

var claszSymbol: Symbol = null
var isCZParcelable = false
Expand All @@ -89,11 +90,12 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
def genPlainClass(cd: ClassDef) {
assert(cnode == null, "GenBCode detected nested methods.")

claszSymbol = cd.symbol
isCZParcelable = isAndroidParcelableClass(claszSymbol)
isCZStaticModule = isStaticModuleClass(claszSymbol)
isCZRemote = isRemote(claszSymbol)
thisBType = classBTypeFromSymbol(claszSymbol)
claszSymbol = cd.symbol
isCZParcelable = isAndroidParcelableClass(claszSymbol)
isCZStaticModule = isStaticModuleClass(claszSymbol)
isCZRemote = isRemote(claszSymbol)
thisBType = classBTypeFromSymbol(claszSymbol)
thisBTypeDescriptor = thisBType.descriptor

cnode = new ClassNode1()

Expand Down Expand Up @@ -194,7 +196,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
val fv =
cnode.visitField(mods,
strMODULE_INSTANCE_FIELD,
thisBType.descriptor,
thisBTypeDescriptor,
null, // no java-generic-signature
null // no initial value
)
Expand Down Expand Up @@ -498,7 +500,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
genDefDef(statified)
} else {
val forwarderDefDef = {
val dd1 = global.gen.mkStatic(deriveDefDef(dd)(_ => EmptyTree), traitSuperAccessorName(sym), _.cloneSymbol.withoutAnnotations)
val dd1 = global.gen.mkStatic(deriveDefDef(dd)(_ => EmptyTree), newTermName(traitSuperAccessorName(sym)), _.cloneSymbol.withoutAnnotations)
dd1.symbol.setFlag(Flags.ARTIFACT).resetFlag(Flags.OVERRIDE)
val selfParam :: realParams = dd1.vparamss.head.map(_.symbol)
deriveDefDef(dd1)(_ =>
Expand Down Expand Up @@ -622,7 +624,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
if (!hasStaticBitSet) {
mnode.visitLocalVariable(
"this",
thisBType.descriptor,
thisBTypeDescriptor,
null,
veryFirstProgramPoint,
onePastLastProgramPoint,
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1098,10 +1098,10 @@ object BTypes {
* @param isEffectivelyFinal True if the class cannot have subclasses: final classes, module
* classes.
*
* @param sam If this class is a SAM type, the SAM's "$name$descriptor".
* @param sam If this class is a SAM type, the SAM's "\$name\$descriptor".
*
* @param methodInfos The [[MethodInlineInfo]]s for the methods declared in this class.
* The map is indexed by the string s"$name$descriptor" (to
* The map is indexed by the string s"\$name\$descriptor" (to
* disambiguate overloads).
*
* @param warning Contains an warning message if an error occurred when building this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ abstract class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass))

if (needsStaticImplMethod(methodSym)) {
val staticName = traitSuperAccessorName(methodSym).toString
val staticName = traitSuperAccessorName(methodSym)
val selfParam = methodSym.newSyntheticValueParam(methodSym.owner.typeConstructor, nme.SELF)
val staticMethodType = methodSym.info match {
case mt@MethodType(params, res) => copyMethodType(mt, selfParam :: params, res)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

package scala.tools.nsc.backend.jvm

import java.io.{BufferedOutputStream, DataOutputStream, FileOutputStream, IOException}
import java.io.{DataOutputStream, IOException}
import java.nio.ByteBuffer
import java.nio.channels.{ClosedByInterruptException, FileChannel}
import java.nio.charset.StandardCharsets
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ package scala.tools.nsc.backend.jvm
* val m = cn.methods.iterator.asScala.find(_.name == "f").head
*
* // the value is read from the classfile, so it's 4
* println(s"maxLocals: ${m.maxLocals}, maxStack: ${m.maxStack}") // maxLocals: 5, maxStack: 4
* println(s"maxLocals: \${m.maxLocals}, maxStack: \${m.maxStack}") // maxLocals: 5, maxStack: 4
*
* // we can safely set it to 2 for running the analyzer.
* m.maxStack = 2
Expand Down
18 changes: 9 additions & 9 deletions src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ abstract class BoxUnbox {
* f(1, 2) // invokes the generic `apply`
* }
*
* The closure optimizer re-writes the `apply` call to `anonfun$adapted` method, which takes
* The closure optimizer re-writes the `apply` call to `anonfun\$adapted` method, which takes
* boxed arguments. After inlining this method, we get
*
* def t2 = {
* val a = boxByte(1)
* val b = boxInteger(2)
* val r = boxInteger(anonfun$(unboxByte(a), unboxInt(b)))
* val r = boxInteger(anonfun\$(unboxByte(a), unboxInt(b)))
* unboxInt(r)
* }
*
Expand Down Expand Up @@ -166,8 +166,8 @@ abstract class BoxUnbox {
*
*
* Special case for tuples wrt specialization: a tuple getter may box or unbox the value stored
* in the tuple: calling `_1` on a `Tuple2$mcII$sp` boxes the primitive Int stored in the tuple.
* Similarly, calling `_1$mcI$sp` on a non-specialized `Tuple2` unboxes the Integer in the tuple.
* in the tuple: calling `_1` on a `Tuple2\$mcII\$sp` boxes the primitive Int stored in the tuple.
* Similarly, calling `_1\$mcI\$sp` on a non-specialized `Tuple2` unboxes the Integer in the tuple.
* When eliminating such getters, we have to introduce appropriate box / unbox calls.
*
*
Expand Down Expand Up @@ -629,7 +629,7 @@ abstract class BoxUnbox {

/**
* If `mi` is an invocation of a method on Predef, check if the receiver is a GETSTATIC of
* Predef.MODULE$ and return it.
* Predef.MODULE\$ and return it.
*/
def checkReceiverPredefLoad(mi: MethodInsnNode, prodCons: ProdConsAnalyzer): Option[AbstractInsnNode] = {
val numArgs = Type.getArgumentTypes(mi.desc).length
Expand Down Expand Up @@ -913,7 +913,7 @@ abstract class BoxUnbox {

/**
* If this box consumer extracts a boxed value and applies a conversion, this method returns
* equivalent conversion operations. For example, invoking `_1$mcI$sp` on a non-specialized
* equivalent conversion operations. For example, invoking `_1\$mcI\$sp` on a non-specialized
* `Tuple2` extracts the Integer value and unboxes it.
*/
def postExtractionAdaptationOps(typeOfExtractedValue: Type): List[AbstractInsnNode] = this match {
Expand All @@ -929,15 +929,15 @@ abstract class BoxUnbox {

/** Static extractor (BoxesRunTime.unboxToInt) or GETFIELD or getter invocation */
case class StaticGetterOrInstanceRead(consumer: AbstractInsnNode) extends BoxConsumer
/** A getter that boxes the returned value, e.g., `Tuple2$mcII$sp._1` */
/** A getter that boxes the returned value, e.g., `Tuple2\$mcII\$sp._1` */
case class PrimitiveBoxingGetter(consumer: MethodInsnNode) extends BoxConsumer
/** A getter that unboxes the returned value, e.g., `Tuple2._1$mcI$sp` */
/** A getter that unboxes the returned value, e.g., `Tuple2._1\$mcI\$sp` */
case class PrimitiveUnboxingGetter(consumer: MethodInsnNode, unboxedPrimitive: Type) extends BoxConsumer
/** An extractor method in a Scala module, e.g., `Predef.Integer2int` */
case class ModuleGetter(moduleLoad: AbstractInsnNode, consumer: MethodInsnNode) extends BoxConsumer
/** PUTFIELD or setter invocation */
case class StaticSetterOrInstanceWrite(consumer: AbstractInsnNode) extends BoxConsumer
/** `.$isInstanceOf[T]` (can be statically proven true or false) */
/** `.\$isInstanceOf[T]` (can be statically proven true or false) */
case class BoxedPrimitiveTypeCheck(consumer: AbstractInsnNode, success: Boolean) extends BoxConsumer
/** An unknown box consumer */
case class EscapingConsumer(consumer: AbstractInsnNode) extends BoxConsumer
Expand Down
53 changes: 32 additions & 21 deletions src/compiler/scala/tools/nsc/classpath/AggregateClassPath.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
package scala.tools.nsc.classpath

import java.net.URL

import scala.collection.mutable.ArrayBuffer
import scala.reflect.internal.FatalError
import scala.reflect.io.AbstractFile
import scala.tools.nsc.util.ClassPath
import scala.tools.nsc.util.ClassRepresentation
import scala.tools.nsc.util.{ClassPath, ClassRepresentation, EfficientClassPath}

/**
* A classpath unifying multiple class- and sourcepath entries.
Expand All @@ -30,21 +30,21 @@ import scala.tools.nsc.util.ClassRepresentation
case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath {
override def findClassFile(className: String): Option[AbstractFile] = {
val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className)
aggregatesForPackage(pkg).iterator.map(_.findClassFile(className)).collectFirst {
aggregatesForPackage(PackageName(pkg)).iterator.map(_.findClassFile(className)).collectFirst {
case Some(x) => x
}
}
private[this] val packageIndex: collection.mutable.Map[String, Seq[ClassPath]] = collection.mutable.Map()
private def aggregatesForPackage(pkg: String): Seq[ClassPath] = packageIndex.synchronized {
packageIndex.getOrElseUpdate(pkg, aggregates.filter(_.hasPackage(pkg)))
private def aggregatesForPackage(pkg: PackageName): Seq[ClassPath] = packageIndex.synchronized {
packageIndex.getOrElseUpdate(pkg.dottedString, aggregates.filter(_.hasPackage(pkg)))
}

// This method is performance sensitive as it is used by SBT's ExtractDependencies phase.
override def findClass(className: String): Option[ClassRepresentation] = {
val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className)

def findEntry(isSource: Boolean): Option[ClassRepresentation] = {
aggregatesForPackage(pkg).iterator.map(_.findClass(className)).collectFirst {
aggregatesForPackage(PackageName(pkg)).iterator.map(_.findClass(className)).collectFirst {
case Some(s: SourceFileEntry) if isSource => s
case Some(s: ClassFileEntry) if !isSource => s
}
Expand All @@ -66,31 +66,44 @@ case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath {

override def asSourcePathString: String = ClassPath.join(aggregates map (_.asSourcePathString): _*)

override private[nsc] def packages(inPackage: String): Seq[PackageEntry] = {
override private[nsc] def packages(inPackage: PackageName): Seq[PackageEntry] = {
val aggregatedPackages = aggregates.flatMap(_.packages(inPackage)).distinct
aggregatedPackages
}

override private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] =
override private[nsc] def classes(inPackage: PackageName): Seq[ClassFileEntry] =
getDistinctEntries(_.classes(inPackage))

override private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] =
override private[nsc] def sources(inPackage: PackageName): Seq[SourceFileEntry] =
getDistinctEntries(_.sources(inPackage))

override private[nsc] def hasPackage(pkg: String) = aggregates.exists(_.hasPackage(pkg))
override private[nsc] def list(inPackage: String): ClassPathEntries = {
val (packages, classesAndSources) = aggregates.map { cp =>
override private[nsc] def hasPackage(pkg: PackageName) = aggregates.exists(_.hasPackage(pkg))
override private[nsc] def list(inPackage: PackageName): ClassPathEntries = {
val packages: java.util.HashSet[PackageEntry] = new java.util.HashSet[PackageEntry]()
val classesAndSourcesBuffer = collection.mutable.ArrayBuffer[ClassRepresentation]()
val onPackage: PackageEntry => Unit = packages.add(_)
val onClassesAndSources: ClassRepresentation => Unit = classesAndSourcesBuffer += _

aggregates.foreach { cp =>
try {
cp.list(inPackage)
cp match {
case ecp: EfficientClassPath =>
ecp.list(inPackage, onPackage, onClassesAndSources)
case _ =>
val entries = cp.list(inPackage)
entries._1.foreach(entry => packages.add(entry))
classesAndSourcesBuffer ++= entries._2
}
} catch {
case ex: java.io.IOException =>
val e = FatalError(ex.getMessage)
e.initCause(ex)
throw e
}
}.unzip
val distinctPackages = packages.flatten.distinct
val distinctClassesAndSources = mergeClassesAndSources(classesAndSources)
}

val distinctPackages: Seq[PackageEntry] = if (packages == null) Nil else packages.toArray(new Array[PackageEntry](packages.size()))
val distinctClassesAndSources = mergeClassesAndSources(classesAndSourcesBuffer)
ClassPathEntries(distinctPackages, distinctClassesAndSources)
}

Expand All @@ -99,14 +112,12 @@ case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath {
* creates an entry containing both of them. If there would be more than one class or source
* entries for the same class it always would use the first entry of each type found on a classpath.
*/
private def mergeClassesAndSources(entries: Seq[Seq[ClassRepresentation]]): Seq[ClassRepresentation] = {
private def mergeClassesAndSources(entries: Seq[ClassRepresentation]): Seq[ClassRepresentation] = {
var count = 0
val indices = collection.mutable.HashMap[String, Int]()
val mergedEntries = new ArrayBuffer[ClassRepresentation](1024)

val mergedEntries = new ArrayBuffer[ClassRepresentation](entries.size)
for {
partOfEntries <- entries
entry <- partOfEntries
entry <- entries
} {
val name = entry.name
if (indices contains name) {
Expand Down
19 changes: 17 additions & 2 deletions src/compiler/scala/tools/nsc/classpath/ClassPath.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@ trait SourceFileEntry extends ClassRepresentation {
def file: AbstractFile
}

case class PackageName(dottedString: String) {
def isRoot: Boolean = dottedString.isEmpty
val dirPathTrailingSlash: String = FileUtils.dirPath(dottedString) + "/"

def entryName(entry: String): String = {
if (isRoot) entry else {
val builder = new java.lang.StringBuilder(dottedString.length + 1 + entry.length)
builder.append(dottedString)
builder.append('.')
builder.append(entry)
builder.toString
}
}
}

trait PackageEntry {
def name: String
}
Expand Down Expand Up @@ -61,10 +76,10 @@ private[nsc] case class PackageEntryImpl(name: String) extends PackageEntry

private[nsc] trait NoSourcePaths {
final def asSourcePathString: String = ""
final private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] = Seq.empty
final private[nsc] def sources(inPackage: PackageName): Seq[SourceFileEntry] = Seq.empty
}

private[nsc] trait NoClassPaths {
final def findClassFile(className: String): Option[AbstractFile] = None
private[nsc] final def classes(inPackage: String): Seq[ClassFileEntry] = Seq.empty
private[nsc] final def classes(inPackage: PackageName): Seq[ClassFileEntry] = Seq.empty
}
Loading