From 57dbd6ab52d45ec65b5c10dcdd5ff7e5de86b90c Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 24 Apr 2019 12:37:32 +1000 Subject: [PATCH 1/2] Support name table sharing across Global instances --- src/reflect/scala/reflect/api/Names.scala | 106 +++++++----- .../scala/reflect/internal/Names.scala | 159 ++++++++++++------ .../scala/reflect/internal/Symbols.scala | 4 +- 3 files changed, 173 insertions(+), 96 deletions(-) diff --git a/src/reflect/scala/reflect/api/Names.scala b/src/reflect/scala/reflect/api/Names.scala index 818ff985729a..6058653ab3e1 100644 --- a/src/reflect/scala/reflect/api/Names.scala +++ b/src/reflect/scala/reflect/api/Names.scala @@ -55,14 +55,75 @@ trait Names { @deprecated("use explicit `TypeName(s)` instead", "2.11.0") implicit def stringToTypeName(s: String): TypeName = TypeName(s) + type NameTable <: NameTableApi + + val nameTable: NameTable + /** The abstract type of names. * @group Names */ - type Name >: Null <: AnyRef with NameApi + type Name >: Null <: AnyRef with nameTable.NameApi /** The abstract type of names representing terms. * @group Names */ + type TypeName >: Null <: nameTable.TypeNameApi with Name + + /** The abstract type of names representing types. + * @group Names + */ + type TermName >: Null <: nameTable.TermNameApi with Name + + /** The API of Name instances. + * @group API + */ + type NameApi <: nameTable.NameApi + + /** Create a new term name. + * @group Names + */ + @deprecated("use TermName instead", "2.11.0") + def newTermName(s: String): TermName + + /** Creates a new type name. + * @group Names + */ + @deprecated("use TypeName instead", "2.11.0") + def newTypeName(s: String): TypeName + + /** The constructor/extractor for `TermName` instances. + * @group Extractors + */ + val TermName: TermNameExtractor + + /** An extractor class to create and pattern match with syntax `TermName(s)`. + * @group Extractors + */ + abstract class TermNameExtractor { + def apply(s: String): TermName + def unapply(name: TermName): Option[String] + } + + /** The constructor/extractor for `TypeName` instances. + * @group Extractors + */ + val TypeName: TypeNameExtractor + + /** An extractor class to create and pattern match with syntax `TypeName(s)`. + * @group Extractors + */ + abstract class TypeNameExtractor { + def apply(s: String): TypeName + def unapply(name: TypeName): Option[String] + } +} + +trait NameTableApi { + /** The abstract type of names. + * @group Names + */ + type Name >: Null <: AnyRef with NameApi + type TypeName >: Null <: TypeNameApi with Name /** Has no special methods. Is here to provides erased identity for `TypeName`. @@ -74,15 +135,11 @@ trait Names { * @group Names */ type TermName >: Null <: TermNameApi with Name - /** Has no special methods. Is here to provides erased identity for `TermName`. * @group API */ trait TermNameApi - /** The API of Name instances. - * @group API - */ abstract class NameApi { /** Checks whether the name is a term name */ def isTermName: Boolean @@ -117,41 +174,4 @@ trait Names { def encodedName: Name } - /** Create a new term name. - * @group Names - */ - @deprecated("use TermName instead", "2.11.0") - def newTermName(s: String): TermName - - /** Creates a new type name. - * @group Names - */ - @deprecated("use TypeName instead", "2.11.0") - def newTypeName(s: String): TypeName - - /** The constructor/extractor for `TermName` instances. - * @group Extractors - */ - val TermName: TermNameExtractor - - /** An extractor class to create and pattern match with syntax `TermName(s)`. - * @group Extractors - */ - abstract class TermNameExtractor { - def apply(s: String): TermName - def unapply(name: TermName): Option[String] - } - - /** The constructor/extractor for `TypeName` instances. - * @group Extractors - */ - val TypeName: TypeNameExtractor - - /** An extractor class to create and pattern match with syntax `TypeName(s)`. - * @group Extractors - */ - abstract class TypeNameExtractor { - def apply(s: String): TypeName - def unapply(name: TypeName): Option[String] - } -} +} \ No newline at end of file diff --git a/src/reflect/scala/reflect/internal/Names.scala b/src/reflect/scala/reflect/internal/Names.scala index 146c123639e3..8e9e8548bed5 100644 --- a/src/reflect/scala/reflect/internal/Names.scala +++ b/src/reflect/scala/reflect/internal/Names.scala @@ -15,10 +15,112 @@ package reflect package internal import scala.language.implicitConversions - import scala.io.Codec +import scala.reflect.api.NameTableApi trait Names extends api.Names { + type NameTable = scala.reflect.internal.NameTable + + override val nameTable: NameTable = newNameTable + protected def synchronizeNames: Boolean = false + protected def newNameTable: NameTable = new NameTable(synchronizeNames) + + type Name = nameTable.Name + type TermName = nameTable.TermName + type TypeName = nameTable.TypeName + + implicit val NameTag = ClassTag[Name](classOf[Name]) + + implicit val TermNameTag = ClassTag[TermName](classOf[TermName]) + object TermName extends TermNameExtractor { + def apply(s: String) = newTermName(s) + def unapply(name: TermName): Option[String] = Some(name.toString) + } + implicit val TypeNameTag = ClassTag[TypeName](classOf[TypeName]) + object TypeName extends TypeNameExtractor { + def apply(s: String) = newTypeName(s) + def unapply(name: TypeName): Option[String] = Some(name.toString) + } + + final def nameTableSize: Int = nameTable.nameTableSize + final def allNames(): Iterator[TermName] = nameTable.allNames() + + final def newTermName(cs: Array[Char], offset: Int, len: Int): TermName = + newTermName(cs, offset, len, cachedString = null) + + final def newTermName(cs: Array[Char]): TermName = nameTable.newTermName(cs) + + final def newTypeName(cs: Array[Char]): TypeName = nameTable.newTypeName(cs) + + /** Create a term name from the characters in cs[offset..offset+len-1]. + * TODO - have a mode where name validation is performed at creation time + * (e.g. if a name has the string "$class" in it, then fail if that + * string is not at the very end.) + * + * @param len0 the length of the name. Negative lengths result in empty names. + */ + final def newTermName(cs: Array[Char], offset: Int, len0: Int, cachedString: String): TermName = nameTable.newTermName(cs, offset, len0, cachedString) + + final def newTypeName(cs: Array[Char], offset: Int, len: Int, cachedString: String): TypeName = nameTable.newTypeName(cs, offset, len, cachedString) + + /** Create a term name from string. */ + @deprecatedOverriding("To synchronize, use `override def synchronizeNames = true`", "2.11.0") // overridden in https://github.com/scala-ide/scala-ide/blob/master/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaPresentationCompiler.scala + final def newTermName(s: String): TermName = nameTable.newTermName(s) + + /** Create a type name from string. */ + @deprecatedOverriding("To synchronize, use `override def synchronizeNames = true`", "2.11.0") // overridden in https://github.com/scala-ide/scala-ide/blob/master/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaPresentationCompiler.scala + final def newTypeName(s: String): TypeName = nameTable.newTypeName(s) + + /** Create a term name from the UTF8 encoded bytes in bs[offset..offset+len-1]. */ + final def newTermName(bs: Array[Byte], offset: Int, len: Int): TermName = nameTable.newTermName(bs, offset, len) + + final def newTermNameCached(s: String): TermName = nameTable.newTermNameCached(s) + + final def newTypeNameCached(s: String): TypeName = nameTable.newTypeNameCached(s) + + /** Create a type name from the characters in cs[offset..offset+len-1]. */ + final def newTypeName(cs: Array[Char], offset: Int, len: Int): TypeName = nameTable.newTypeName(cs, offset, len) + + /** Create a type name from the UTF8 encoded bytes in bs[offset..offset+len-1]. */ + final def newTypeName(bs: Array[Byte], offset: Int, len: Int): TypeName = nameTable.newTypeName(bs, offset, len) + + /** + * Used by test code only. + */ + final def lookupTypeName(cs: Array[Char]): TypeName = nameTable.lookupTypeName(cs) + + implicit def AnyNameOps(name: Name): NameOps[Name] = new NameOps(name) + implicit def TermNameOps(name: TermName): NameOps[TermName] = new NameOps(name) + implicit def TypeNameOps(name: TypeName): NameOps[TypeName] = new NameOps(name) + + /** FIXME: This is a good example of something which is pure "value class" but cannot + * reap the benefits because an (unused) $outer pointer so it is not single-field. + */ + final class NameOps[T <: Name](name: T) { + import NameTransformer._ + def stripSuffix(suffix: String): T = if (name endsWith suffix) dropRight(suffix.length) else name // OPT avoid creating a Name with `suffix` + def stripSuffix(suffix: Name): T = if (name endsWith suffix) dropRight(suffix.length) else name + def take(n: Int): T = name.subName(0, n).asInstanceOf[T] + def drop(n: Int): T = name.subName(n, name.length).asInstanceOf[T] + def dropRight(n: Int): T = name.subName(0, name.length - n).asInstanceOf[T] + def dropLocal: TermName = name.toTermName stripSuffix LOCAL_SUFFIX_STRING + def dropSetter: TermName = name.toTermName stripSuffix SETTER_SUFFIX_STRING + def dropModule: T = this stripSuffix MODULE_SUFFIX_STRING + def localName: TermName = getterName append LOCAL_SUFFIX_STRING + def setterName: TermName = getterName append SETTER_SUFFIX_STRING + def getterName: TermName = dropTraitSetterSeparator.dropSetter.dropLocal + + private def dropTraitSetterSeparator: TermName = + name indexOf TRAIT_SETTER_SEPARATOR_STRING match { + case -1 => name.toTermName + case idx => name.toTermName drop idx drop TRAIT_SETTER_SEPARATOR_STRING.length + } + } + + +} + +final class NameTable(synchronizeNames: Boolean) extends NameTableApi { private final val HASH_SIZE = 0x8000 private final val HASH_MASK = 0x7FFF private final val NAME_SIZE = 0x20000 @@ -34,7 +136,6 @@ trait Names extends api.Names { // detect performance regressions. // // Discussion: https://groups.google.com/forum/#!search/biased$20scala-internals/scala-internals/0cYB7SkJ-nM/47MLhsgw8jwJ - protected def synchronizeNames: Boolean = false private[this] val nameLock: Object = new Object /** Memory to store all names sequentially. */ @@ -48,7 +149,9 @@ trait Names extends api.Names { /** Hashtable for finding type names quickly. */ private[this] val typeHashtable = new Array[TypeName](HASH_SIZE) - final def allNames(): Iterator[TermName] = termHashtable.iterator.filter(_ ne null).flatMap(n => Iterator.iterate(n)(_.next).takeWhile(_ ne null)) + final def allNames(): Iterator[TermName] = nameLock.synchronized { + termHashtable.iterator.filter(_ ne null).flatMap(n => Iterator.iterate(n)(_.next).takeWhile(_ ne null)) + } /** * The hashcode of a name depends on the first, the last and the middle character, @@ -141,12 +244,10 @@ trait Names extends api.Names { newTermName(cs, offset, len, cachedString).toTypeName /** Create a term name from string. */ - @deprecatedOverriding("To synchronize, use `override def synchronizeNames = true`", "2.11.0") // overridden in https://github.com/scala-ide/scala-ide/blob/master/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaPresentationCompiler.scala - def newTermName(s: String): TermName = newTermName(s.toCharArray(), 0, s.length(), null) + final def newTermName(s: String): TermName = newTermName(s.toCharArray(), 0, s.length(), null) /** Create a type name from string. */ - @deprecatedOverriding("To synchronize, use `override def synchronizeNames = true`", "2.11.0") // overridden in https://github.com/scala-ide/scala-ide/blob/master/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaPresentationCompiler.scala - def newTypeName(s: String): TypeName = newTermName(s).toTypeName + final def newTypeName(s: String): TypeName = newTermName(s).toTypeName /** Create a term name from the UTF8 encoded bytes in bs[offset..offset+len-1]. */ final def newTermName(bs: Array[Byte], offset: Int, len: Int): TermName = { @@ -475,36 +576,6 @@ trait Names extends api.Names { def debugString = { val s = decode ; if (isTypeName) s + "!" else s } } - implicit def AnyNameOps(name: Name): NameOps[Name] = new NameOps(name) - implicit def TermNameOps(name: TermName): NameOps[TermName] = new NameOps(name) - implicit def TypeNameOps(name: TypeName): NameOps[TypeName] = new NameOps(name) - - /** FIXME: This is a good example of something which is pure "value class" but cannot - * reap the benefits because an (unused) $outer pointer so it is not single-field. - */ - final class NameOps[T <: Name](name: T) { - import NameTransformer._ - def stripSuffix(suffix: String): T = if (name endsWith suffix) dropRight(suffix.length) else name // OPT avoid creating a Name with `suffix` - def stripSuffix(suffix: Name): T = if (name endsWith suffix) dropRight(suffix.length) else name - def take(n: Int): T = name.subName(0, n).asInstanceOf[T] - def drop(n: Int): T = name.subName(n, name.length).asInstanceOf[T] - def dropRight(n: Int): T = name.subName(0, name.length - n).asInstanceOf[T] - def dropLocal: TermName = name.toTermName stripSuffix LOCAL_SUFFIX_STRING - def dropSetter: TermName = name.toTermName stripSuffix SETTER_SUFFIX_STRING - def dropModule: T = this stripSuffix MODULE_SUFFIX_STRING - def localName: TermName = getterName append LOCAL_SUFFIX_STRING - def setterName: TermName = getterName append SETTER_SUFFIX_STRING - def getterName: TermName = dropTraitSetterSeparator.dropSetter.dropLocal - - private def dropTraitSetterSeparator: TermName = - name indexOf TRAIT_SETTER_SEPARATOR_STRING match { - case -1 => name.toTermName - case idx => name.toTermName drop idx drop TRAIT_SETTER_SEPARATOR_STRING.length - } - } - - implicit val NameTag = ClassTag[Name](classOf[Name]) - /** A name that contains no operator chars nor dollar signs. * TODO - see if it's any faster to do something along these lines. * Cute: now that exhaustivity kind of works, the mere presence of @@ -575,13 +646,6 @@ trait Names extends api.Names { protected def createCompanionName(next: TypeName): TypeName } - implicit val TermNameTag = ClassTag[TermName](classOf[TermName]) - - object TermName extends TermNameExtractor { - def apply(s: String) = newTermName(s) - def unapply(name: TermName): Option[String] = Some(name.toString) - } - sealed abstract class TypeName(index0: Int, len0: Int, val next: TypeName) extends Name(index0, len0) with TypeNameApi { type ThisNameType = TypeName protected[this] def thisName: TypeName = this @@ -610,11 +674,4 @@ trait Names extends api.Names { def nameKind = "type" override def decode = if (nameDebug) super.decode + "!" else super.decode } - - implicit val TypeNameTag = ClassTag[TypeName](classOf[TypeName]) - - object TypeName extends TypeNameExtractor { - def apply(s: String) = newTypeName(s) - def unapply(name: TypeName): Option[String] = Some(name.toString) - } } diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index c3a510812fb9..34c89f2381cb 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -1305,11 +1305,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => if (sym.isRoot || sym.isRootPackage || sym == NoSymbol || sym.owner.isEffectiveRoot) { val capacity = size + nSize b = new java.lang.StringBuffer(capacity) - b.append(chrs, symName.start, nSize) + b.append(nameTable.chrs, symName.start, nSize) } else { loop(size + nSize + 1, sym.effectiveOwner.enclClass) b.append(separator) - b.append(chrs, symName.start, nSize) + b.append(nameTable.chrs, symName.start, nSize) } } loop(suffix.length(), this) From 12749de4a5cdc8607ff83678871f935d9524a2f5 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 24 Apr 2019 12:49:04 +1000 Subject: [PATCH 2/2] Add UI to enable name table sharing --- src/compiler/scala/tools/nsc/Global.scala | 13 ++++++++++++- .../scala/tools/nsc/settings/ScalaSettings.scala | 1 + src/reflect/scala/reflect/internal/Names.scala | 4 +++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 45d2b752c2ca..70f948da6d80 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -23,7 +23,7 @@ import io.{AbstractFile, SourceReader} import util.{ClassPath, returning} import reporters.{Reporter => LegacyReporter} import scala.reflect.ClassTag -import scala.reflect.internal.{Reporter => InternalReporter} +import scala.reflect.internal.{NameTable, Reporter => InternalReporter} import scala.reflect.internal.util.{BatchSourceFile, FreshNameCreator, NoSourceFile, ScriptSourceFile, SourceFile} import scala.reflect.internal.pickling.PickleBuffer import symtab.{Flags, SymbolTable, SymbolTrackers} @@ -41,6 +41,7 @@ import scala.tools.nsc.classpath._ import scala.tools.nsc.profile.Profiler import scala.util.control.NonFatal import java.io.Closeable + import scala.annotation.tailrec class Global(var currentSettings: Settings, reporter0: LegacyReporter) @@ -60,6 +61,13 @@ class Global(var currentSettings: Settings, reporter0: LegacyReporter) override def isCompilerUniverse = true override val useOffsetPositions = !currentSettings.Yrangepos + override protected def newNameTable: NameTable = { + if (currentSettings.YcacheNameTable) { + val NoFiles = Nil + Global.nameTableCache.getOrCreate(NoFiles, () => new NameTable(synchronizeNames = true), closeableRegistry, checkStamps = true) + } + else super.newNameTable + } type RuntimeClass = java.lang.Class[_] implicit val RuntimeClassTag: ClassTag[RuntimeClass] = ClassTag[RuntimeClass](classOf[RuntimeClass]) @@ -1743,4 +1751,7 @@ object Global { override def keepsTypeParams = false def run(): Unit = { throw new Error("InitPhase.run") } } + + // TODO factor reference counting caching out of FileBasedCache for use in spots like this. + private val nameTableCache = new FileBasedCache[NameTable] } diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 8b8b589021ac..1bf0033fe946 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -251,6 +251,7 @@ trait ScalaSettings extends AbsScalaSettings val YcachePluginClassLoader = CachePolicy.setting("plugin", "compiler plugins") val YcacheMacroClassLoader = CachePolicy.setting("macro", "macros") val YmacroClasspath = PathSetting ("-Ymacro-classpath", "The classpath used to reflectively load macro implementations, default is the compilation classpath.", "") + val YcacheNameTable = BooleanSetting ("-Ycache-name-table", "Share a single name table for concurrently running instances of the compiler") val exposeEmptyPackage = BooleanSetting ("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly() val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "method") diff --git a/src/reflect/scala/reflect/internal/Names.scala b/src/reflect/scala/reflect/internal/Names.scala index 8e9e8548bed5..dd559cdb079d 100644 --- a/src/reflect/scala/reflect/internal/Names.scala +++ b/src/reflect/scala/reflect/internal/Names.scala @@ -23,7 +23,9 @@ trait Names extends api.Names { override val nameTable: NameTable = newNameTable protected def synchronizeNames: Boolean = false - protected def newNameTable: NameTable = new NameTable(synchronizeNames) + protected def newNameTable: NameTable = { + new NameTable(synchronizeNames) + } type Name = nameTable.Name type TermName = nameTable.TermName