diff options
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.scala | 82 |
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))) } } } |