Skip to content

Reduce allocations when loading packages from classpath #9587

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

Merged
merged 6 commits into from
Aug 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions compiler/src/dotty/tools/dotc/classpath/ClassPath.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,27 @@ trait PackageEntry {
}

private[dotty] case class ClassFileEntryImpl(file: AbstractFile) extends ClassFileEntry {
override def name: String = FileUtils.stripClassExtension(file.name) // class name
final def fileName: String = file.name
def name: String = FileUtils.stripClassExtension(file.name) // class name

override def binary: Option[AbstractFile] = Some(file)
override def source: Option[AbstractFile] = None
def binary: Option[AbstractFile] = Some(file)
def source: Option[AbstractFile] = None
}

private[dotty] case class SourceFileEntryImpl(file: AbstractFile) extends SourceFileEntry {
override def name: String = FileUtils.stripSourceExtension(file.name)
final def fileName: String = file.name
def name: String = FileUtils.stripSourceExtension(file.name)

override def binary: Option[AbstractFile] = None
override def source: Option[AbstractFile] = Some(file)
def binary: Option[AbstractFile] = None
def source: Option[AbstractFile] = Some(file)
}

private[dotty] case class ClassAndSourceFilesEntry(classFile: AbstractFile, srcFile: AbstractFile) extends ClassRepresentation {
override def name: String = FileUtils.stripClassExtension(classFile.name)
final def fileName: String = classFile.name
def name: String = FileUtils.stripClassExtension(classFile.name)

override def binary: Option[AbstractFile] = Some(classFile)
override def source: Option[AbstractFile] = Some(srcFile)
def binary: Option[AbstractFile] = Some(classFile)
def source: Option[AbstractFile] = Some(srcFile)
}

private[dotty] case class PackageEntryImpl(name: String) extends PackageEntry
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package config
import core._
import Contexts._, Symbols._, Names._, NameOps._, Phases._
import StdNames.nme
import Decorators.{given _}
import Decorators.{_, given _}
import util.SrcPos
import SourceVersion._
import reporting.Message
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/SourceVersion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package config
import core.Contexts._
import core.Names.TermName
import core.StdNames.nme
import core.Decorators.{given _}
import core.Decorators.{_, given _}
import util.Property

enum SourceVersion:
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,8 @@ object Contexts {
private[Contexts] val comparers = new mutable.ArrayBuffer[TypeComparer]
private[Contexts] var comparersInUse: Int = 0

private[dotc] var nameCharBuffer = new Array[Char](256)

def reset(): Unit = {
for ((_, set) <- uniqueSets) set.clear()
errorTypeMsg.clear()
Expand Down
28 changes: 20 additions & 8 deletions compiler/src/dotty/tools/dotc/core/Decorators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,34 @@ object Decorators {
* with a normal wildcard. In the future, once #9255 is in trunk, replace with
* a simple collective extension.
*/
implicit object PreNamedString:
extension (pn: PreName) def toTypeName: TypeName = pn match
case s: String => typeName(s)
case n: Name => n.toTypeName
extension (pn: PreName) def toTermName: TermName = pn match
extension (pn: PreName)
def toTermName: TermName = pn match
case s: String => termName(s)
case n: Name => n.toTermName
def toTypeName: TypeName = pn match
case s: String => typeName(s)
case n: Name => n.toTypeName

extension (s: String):
def splitWhere(f: Char => Boolean, doDropIndex: Boolean): Option[(String, String)] = {
def splitWhere(f: Char => Boolean, doDropIndex: Boolean): Option[(String, String)] =
def splitAt(idx: Int, doDropIndex: Boolean): Option[(String, String)] =
if (idx == -1) None
else Some((s.take(idx), s.drop(if (doDropIndex) idx + 1 else idx)))

splitAt(s.indexWhere(f), doDropIndex)
}

/** Create a term name from a string slice, using a common buffer.
* This avoids some allocation relative to `termName(s)`
*/
def sliceToTermName(start: Int, end: Int)(using Context): SimpleName =
val base = ctx.base
val len = end - start
while len > base.nameCharBuffer.length do
base.nameCharBuffer = new Array[Char](base.nameCharBuffer.length * 2)
s.getChars(start, end, base.nameCharBuffer, 0)
termName(base.nameCharBuffer, 0, len)

def sliceToTypeName(start: Int, end: Int)(using Context): TypeName =
sliceToTermName(start, end).toTypeName

/** Implements a findSymbol method on iterators of Symbols that
* works like find but avoids Option, replacing None with NoSymbol.
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -682,10 +682,10 @@ class Definitions {
@tu lazy val NonLocalReturnControlClass: ClassSymbol = requiredClass("scala.runtime.NonLocalReturnControl")
@tu lazy val SelectableClass: ClassSymbol = requiredClass("scala.Selectable")

@tu lazy val ReflectPackageClass: Symbol = requiredPackage("scala.reflect.package").moduleClass
@tu lazy val ClassTagClass: ClassSymbol = requiredClass("scala.reflect.ClassTag")
@tu lazy val ClassTagModule: Symbol = ClassTagClass.companionModule
@tu lazy val ClassTagModule_apply: Symbol = ClassTagModule.requiredMethod(nme.apply)
@tu lazy val ReflectPackageClass: Symbol = requiredPackage("scala.reflect.package").moduleClass


@tu lazy val QuotedExprClass: ClassSymbol = requiredClass("scala.quoted.Expr")
Expand Down
7 changes: 5 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Names.scala
Original file line number Diff line number Diff line change
Expand Up @@ -638,10 +638,13 @@ object Names {
def typeName(bs: Array[Byte], offset: Int, len: Int): TypeName =
termName(bs, offset, len).toTypeName

/** Create a term name from a string, without encoding operators */
/** Create a term name from a string.
* See `sliceToTermName` in `Decorators` for a more efficient version
* which however requires a Context for its operation.
*/
def termName(s: String): SimpleName = termName(s.toCharArray, 0, s.length)

/** Create a type name from a string, without encoding operators */
/** Create a type name from a string */
def typeName(s: String): TypeName = typeName(s.toCharArray, 0, s.length)

table(0) = new SimpleName(-1, 0, null)
Expand Down
38 changes: 23 additions & 15 deletions compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,29 @@ package core

import java.io.{IOException, File}
import java.nio.channels.ClosedByInterruptException

import scala.compat.Platform.currentTime
import scala.util.control.NonFatal

import dotty.tools.io.{ ClassPath, ClassRepresentation, AbstractFile }
import config.Config
import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions

import Contexts._, Symbols._, Flags._, SymDenotations._, Types._, Scopes._, Names._
import NameOps._
import StdNames.str
import Decorators._
import StdNames._
import classfile.ClassfileParser
import util.Stats
import Decorators._
import scala.util.control.NonFatal

import util.Stats
import reporting.trace
import config.Config

import ast.Trees._
import ast.desugar

import parsing.JavaParsers.OutlineJavaParser
import parsing.Parsers.OutlineParser
import reporting.trace
import ast.desugar

import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions

object SymbolLoaders {
import ast.untpd._
Expand Down Expand Up @@ -178,27 +183,30 @@ object SymbolLoaders {
* Note: We do a name-base comparison here because the method is called before we even
* have ReflectPackage defined.
*/
def binaryOnly(owner: Symbol, name: String)(using Context): Boolean =
name == "package" &&
(owner.fullName.toString == "scala" || owner.fullName.toString == "scala.reflect")
def binaryOnly(owner: Symbol, name: TermName)(using Context): Boolean =
name == nme.PACKAGEkw &&
(owner.name == nme.scala || owner.name == nme.reflect && owner.owner.name == nme.scala)

/** Initialize toplevel class and module symbols in `owner` from class path representation `classRep`
*/
def initializeFromClassPath(owner: Symbol, classRep: ClassRepresentation)(using Context): Unit =
((classRep.binary, classRep.source): @unchecked) match {
case (Some(bin), Some(src)) if needCompile(bin, src) && !binaryOnly(owner, classRep.name) =>
case (Some(bin), Some(src)) if needCompile(bin, src) && !binaryOnly(owner, nameOf(classRep)) =>
if (ctx.settings.verbose.value) report.inform("[symloader] picked up newer source file for " + src.path)
enterToplevelsFromSource(owner, classRep.name, src)
enterToplevelsFromSource(owner, nameOf(classRep), src)
case (None, Some(src)) =>
if (ctx.settings.verbose.value) report.inform("[symloader] no class, picked up source file for " + src.path)
enterToplevelsFromSource(owner, classRep.name, src)
enterToplevelsFromSource(owner, nameOf(classRep), src)
case (Some(bin), _) =>
enterClassAndModule(owner, classRep.name, ctx.platform.newClassLoader(bin))
enterClassAndModule(owner, nameOf(classRep), ctx.platform.newClassLoader(bin))
}

def needCompile(bin: AbstractFile, src: AbstractFile): Boolean =
src.lastModified >= bin.lastModified

private def nameOf(classRep: ClassRepresentation)(using Context): TermName =
classRep.fileName.sliceToTermName(0, classRep.nameLength)

/** Load contents of a package
*/
class PackageLoader(_sourceModule: TermSymbol, classPath: ClassPath)
Expand Down
10 changes: 10 additions & 0 deletions compiler/src/dotty/tools/io/ClassPath.scala
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,19 @@ object ClassPath {
}

trait ClassRepresentation {
def fileName: String
def name: String
def binary: Option[AbstractFile]
def source: Option[AbstractFile]

/** returns the length of `name` by stripping the extension of `fileName`
*
* Used to avoid creating String instance of `name`.
*/
final def nameLength: Int = {
val ix = fileName.lastIndexOf('.')
if (ix < 0) fileName.length else ix
}
}

@deprecated("shim for sbt's compiler interface", since = "2.12.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import dotty.tools.dotc.ast.tpd.TreeOps
import dotty.tools.dotc.{Driver, Main}
import dotty.tools.dotc.core.Comments.CommentsContext
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Decorators.PreNamedString
import dotty.tools.dotc.core.Decorators.{toTermName, toTypeName}
import dotty.tools.dotc.core.Mode
import dotty.tools.dotc.core.Names.Name
import dotty.tools.dotc.interfaces.Diagnostic.ERROR
Expand Down