summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala')
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala82
1 files changed, 57 insertions, 25 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
index a5b85e54e7..4492d0baf5 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
@@ -10,6 +10,7 @@ package opt
import scala.tools.asm
import asm.tree._
import scala.collection.convert.decorateAsScala._
+import scala.collection.concurrent
import scala.tools.asm.Attribute
import scala.tools.nsc.backend.jvm.BackendReporting._
import scala.tools.nsc.io.AbstractFile
@@ -24,39 +25,52 @@ import java.util.concurrent.atomic.AtomicLong
* 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.
- * The `Long` field encodes the age of the node in the map, which allows removing
- * old entries when the map grows too large.
- * For Java classes in mixed compilation, the map contains an error message: no
- * ClassNode is generated by the backend and also no classfile that could be parsed.
*/
-class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJavaSourceDefined: InternalName => Boolean, val classes: collection.concurrent.Map[InternalName, Either[ClassNotFound, (ClassNode, Source, Long)]]) {
+class ByteCodeRepository[BT <: BTypes](val classPath: ClassFileLookup[AbstractFile], val btypes: BT) {
+ import btypes._
+
+ /**
+ * ClassNodes for classes being compiled in the current compilation run.
+ */
+ val compilingClasses: concurrent.Map[InternalName, ClassNode] = recordPerRunCache(concurrent.TrieMap.empty)
+
+ /**
+ * Cache for parsed ClassNodes.
+ * The `Long` field encodes the age of the node in the map, which allows removing old entries when
+ * the map grows too large (see limitCacheSize).
+ * For Java classes in mixed compilation, the map contains an error message: no ClassNode is
+ * generated by the backend and also no classfile that could be parsed.
+ */
+ val parsedClasses: concurrent.Map[InternalName, Either[ClassNotFound, (ClassNode, Long)]] = recordPerRunCache(concurrent.TrieMap.empty)
private val maxCacheSize = 1500
private val targetSize = 500
- private val idCounter = new AtomicLong(0)
+ private object lruCounter extends AtomicLong(0l) with collection.generic.Clearable {
+ def clear(): Unit = { this.set(0l) }
+ }
+ recordPerRunCache(lruCounter)
/**
* Prevent the code repository from growing too large. Profiling reveals that the average size
* of a ClassNode is about 30 kb. I observed having 17k+ classes in the cache, i.e., 500 mb.
- *
- * We can only remove classes with `Source == Classfile`, those can be parsed again if requested.
*/
private def limitCacheSize(): Unit = {
- if (classes.count(c => c._2.isRight && c._2.right.get._2 == Classfile) > maxCacheSize) {
- val removeId = idCounter.get - targetSize
- val toRemove = classes.iterator.collect({
- case (name, Right((_, Classfile, id))) if id < removeId => name
- }).toList
- toRemove foreach classes.remove
+ if (parsedClasses.size > maxCacheSize) {
+ // OK if multiple threads get here
+ val minimalLRU = parsedClasses.valuesIterator.collect({
+ case Right((_, lru)) => lru
+ }).toList.sorted(Ordering.Long.reverse).drop(targetSize).headOption.getOrElse(Long.MaxValue)
+ parsedClasses retain {
+ case (_, Right((_, lru))) => lru > minimalLRU
+ case _ => false
+ }
}
}
def add(classNode: ClassNode, source: Source) = {
- classes(classNode.name) = Right((classNode, source, idCounter.incrementAndGet()))
+ if (source == CompilationUnit) compilingClasses(classNode.name) = classNode
+ else parsedClasses(classNode.name) = Right((classNode, lruCounter.incrementAndGet()))
}
/**
@@ -64,18 +78,32 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav
* parsed from the classfile on the compile classpath.
*/
def classNodeAndSource(internalName: InternalName): Either[ClassNotFound, (ClassNode, Source)] = {
- val r = classes.getOrElseUpdate(internalName, {
- limitCacheSize()
- parseClass(internalName).map((_, Classfile, idCounter.incrementAndGet()))
+ classNode(internalName) map (n => {
+ val source = if (compilingClasses contains internalName) CompilationUnit else Classfile
+ (n, source)
})
- r.map(v => (v._1, v._2))
}
/**
* 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): Either[ClassNotFound, ClassNode] = classNodeAndSource(internalName).map(_._1)
+ def classNode(internalName: InternalName): Either[ClassNotFound, ClassNode] = {
+ compilingClasses.get(internalName).map(Right(_)) getOrElse {
+ val r = parsedClasses.get(internalName) match {
+ case Some(l @ Left(_)) => l
+ case Some(r @ Right((classNode, _))) =>
+ parsedClasses(internalName) = Right((classNode, lruCounter.incrementAndGet()))
+ r
+ case None =>
+ limitCacheSize()
+ val res = parseClass(internalName).map((_, lruCounter.incrementAndGet()))
+ parsedClasses(internalName) = res
+ res
+ }
+ r.map(_._1)
+ }
+ }
/**
* The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`.
@@ -86,7 +114,6 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav
*/
def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Either[FieldNotFound, (FieldNode, InternalName)] = {
def fieldNodeImpl(parent: InternalName): Either[FieldNotFound, (FieldNode, InternalName)] = {
- def msg = s"The field node $name$descriptor could not be found in class $classInternalName or any of its superclasses."
classNode(parent) match {
case Left(e) => Left(FieldNotFound(name, descriptor, classInternalName, Some(e)))
case Right(c) =>
@@ -105,6 +132,11 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav
* The method node for a method matching `name` and `descriptor`, accessed in class `ownerInternalNameOrArrayDescriptor`.
* The declaration of the method may be in one of the parents.
*
+ * TODO: make sure we always return the right method, the one being invoked. write tests.
+ * - if there's an abstract and a concrete one. could possibly somehow the abstract be returned?
+ * - with traits and default methods, if there is more than one default method inherited and
+ * no override: what should be returned? We should not just inline one of the two.
+ *
* @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring
* class, or an error message if the method could not be found.
*/
@@ -157,7 +189,7 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav
classNode
} match {
case Some(node) => Right(node)
- case None => Left(ClassNotFound(internalName, isJavaSourceDefined(internalName)))
+ case None => Left(ClassNotFound(internalName, javaDefinedClasses(internalName)))
}
}
}