summaryrefslogblamecommitdiff
path: root/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
blob: 0958601d734d24847aba609841d1294b27d3da18 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12











                                                 
                                


                                           
                      

                           
                                             
 







                                                                                                    

                                                                                                  

                                                                                                  
   


























                                                                                                                                                                 
     

                                                                                                  
     
                                                                                     




                                                                               
   




                                                                                                   
                                                                                                           







                                                                                                                         



                                                                                                             







                                                                                                          

                                                                                                                                      
                                                                                   

                                                                 





                                                                                                                                 


     
                                                                           



                                                                  



                                                                                                  

                                                                                                
                                                                                                                







                                                                                                                                          
               

     
 









                                                                                                     
/* 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.asm.Attribute
import scala.tools.nsc.io.AbstractFile
import scala.tools.nsc.util.ClassFileLookup
import OptimizerReporting._
import BytecodeUtils._
import ByteCodeRepository._
import BTypes.InternalName
import java.util.concurrent.atomic.AtomicLong

/**
 * 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.
 *                  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 `None`: there is no
 *                  ClassNode generated by the backend and also no classfile that could be parsed.
 */
class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val classes: collection.concurrent.Map[InternalName, Option[(ClassNode, Source, Long)]]) {

  private val maxCacheSize = 1500
  private val targetSize   = 500

  private val idCounter = new AtomicLong(0)

  /**
   * 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.isDefined && c._2.get._2 == Classfile) > maxCacheSize) {
      val removeId = idCounter.get - targetSize
      val toRemove = classes.iterator.collect({
        case (name, Some((_, Classfile, id))) if id < removeId => name
      }).toList
      toRemove foreach classes.remove
    }
  }

  def add(classNode: ClassNode, source: Source) = {
    classes(classNode.name) = Some((classNode, source, idCounter.incrementAndGet()))
  }

  /**
   * 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.
   */
  def classNodeAndSource(internalName: InternalName): Option[(ClassNode, Source)] = {
    val r = classes.getOrElseUpdate(internalName, {
      limitCacheSize()
      parseClass(internalName).map((_, Classfile, idCounter.incrementAndGet()))
    })
    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): Option[ClassNode] = classNodeAndSource(internalName).map(_._1)

  /**
   * 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)] = {
    classNode(classInternalName).flatMap(c =>
      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(ownerInternalNameOrArrayDescriptor: String, name: String, descriptor: String): Option[(MethodNode, InternalName)] = {
    // In a MethodInsnNode, the `owner` field may be an array descriptor, for exmple when invoking `clone`.
    // We don't inline array methods (they are native anyway), so just return None.
    if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[') None
    else {
      classNode(ownerInternalNameOrArrayDescriptor).flatMap(c =>
        c.methods.asScala.find(m => m.name == name && m.desc == descriptor).map((_, ownerInternalNameOrArrayDescriptor)) 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): Option[ClassNode] = {
    val fullName = internalName.replace('/', '.')
    classPath.findClassFile(fullName) map { classFile =>
      val classNode = new asm.tree.ClassNode()
      val classReader = new asm.ClassReader(classFile.toByteArray)

      // Passing the InlineInfoAttributePrototype makes the ClassReader invoke the specific `read`
      // method of the InlineInfoAttribute class, instead of putting the byte array into a generic
      // Attribute.
      // 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, Array[Attribute](InlineInfoAttributePrototype), 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)
      classNode
    }
  }
}

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
}