From e3f46feb916aa6aa43f6f8fa5bd2a1698eaaeb9c Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 30 Jul 2019 10:25:29 +1000 Subject: [PATCH 1/7] Refactor hot method in WeakHashSet to see if profiles change --- .../reflect/internal/util/WeakHashSet.scala | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/reflect/scala/reflect/internal/util/WeakHashSet.scala b/src/reflect/scala/reflect/internal/util/WeakHashSet.scala index f45c8dcf2a97..53c622cf0678 100644 --- a/src/reflect/scala/reflect/internal/util/WeakHashSet.scala +++ b/src/reflect/scala/reflect/internal/util/WeakHashSet.scala @@ -192,17 +192,13 @@ final class WeakHashSet[A <: AnyRef](val initialCapacity: Int, val loadFactor: D elem } - @tailrec - def linkedListLoop(entry: Entry[A]): A = entry match { - case null => add() - case _ => { - val entryElem = entry.get - if (elem.equals(entryElem)) entryElem - else linkedListLoop(entry.tail) - } + var entry: Entry[A] = oldHead + while (entry ne null) { + val entryElem = entry.get + if (elem.equals(entryElem)) return entryElem + entry = entry.tail } - - linkedListLoop(oldHead) + add() } } From b15f7057266a22f36aeb7f6db4f8031b670df630 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 17 Mar 2017 12:47:43 +1000 Subject: [PATCH 2/7] dont unique types containig type vars (cherry picked from commit 27d841299d19bb63aca9f4cc18e93cfcd55d6b15) --- .../scala/reflect/internal/Types.scala | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 381b9ff350e4..8a7f3e3c6370 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -27,6 +27,7 @@ import util.ThreeValues._ import Variance._ import Depth._ import TypeConstants._ +import scala.util.hashing.MurmurHash3 /* A standard type pattern match: case ErrorType => @@ -1080,7 +1081,26 @@ trait Types */ abstract class UniqueType extends Type with Product { final override val hashCode = computeHashCode - protected def computeHashCode = scala.runtime.ScalaRunTime._hashCode(this) + // DUPLICATED from MurmurHash3.productHash to replace ## with hashCode + protected def computeHashCode: Int = { + val seed = MurmurHash3.productSeed + val arr = productArity + val result = if (arr == 0) { + productPrefix.hashCode + } + else { + var h = seed + var i = 0 + while (i < arr) { + val elementHashCode = productElement(i).hashCode() + if (elementHashCode == PoisonHashCode) return PoisonHashCode + h = MurmurHash3.mix(h, productElement(i).hashCode()) + i += 1 + } + MurmurHash3.finalizeHash(h, arr) + } + avoidPoisonHashCode(result) + } } /** A base class for types that defer some operations @@ -2102,19 +2122,21 @@ trait Types private var normalized: Type = _ //OPT specialize hashCode - override final def computeHashCode = { + override final def computeHashCode: Int = { import scala.util.hashing.MurmurHash3._ var h = productSeed h = mix(h, pre.hashCode) h = mix(h, sym.hashCode) - var length = 2 - var elems = args - while (elems ne Nil) { - h = mix(h, elems.head.hashCode()) - elems = elems.tail - length += 1 + var i = 0 + var elem = args + while (elem ne Nil) { + val elemHashCode = elem.head.hashCode() + if (elemHashCode == PoisonHashCode) return PoisonHashCode + h = mix(h, elemHashCode) + elem = elem.tail + i += 1 } - finalizeHash(h, length) + avoidPoisonHashCode(finalizeHash(h, 2 + i)) } //OPT specialize equals override final def equals(other: Any): Boolean = { @@ -3005,6 +3027,8 @@ trait Types } } + final private val PoisonHashCode = Int.MinValue + final private def avoidPoisonHashCode(code: Int): Int = if (code == PoisonHashCode) code + 1 else code /** A class representing a type variable: not used after phase `typer`. * * A higher-kinded TypeVar has params (Symbols) and typeArgs (Types). @@ -3021,7 +3045,7 @@ trait Types // We don't want case class equality/hashing as TypeVar-s are mutable, // and TypeRefs based on them get wrongly `uniqued` otherwise. See scala/bug#7226. - override def hashCode(): Int = System.identityHashCode(this) + override def hashCode(): Int = PoisonHashCode override def equals(other: Any): Boolean = this eq other.asInstanceOf[AnyRef] def untouchable = false // by other typevars @@ -3820,7 +3844,7 @@ trait Types final def howManyUniqueTypes: Int = if (uniques == null) 0 else uniques.size - protected def unique[T <: Type](tp: T): T = { + protected def unique[T <: Type](tp: T): T = if (tp.hashCode() == PoisonHashCode) tp else{ if (StatisticsStatics.areSomeColdStatsEnabled) statistics.incCounter(rawTypeCount) if (uniqueRunId != currentRunId) { uniques = util.WeakHashSet[Type](initialUniquesCapacity) From 5beb6bc23718c7732541d069155b0b2cc7b0cd63 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 30 Jul 2019 11:18:12 +1000 Subject: [PATCH 3/7] Remove some hot stats --- src/compiler/scala/tools/nsc/Global.scala | 2 +- src/reflect/scala/reflect/internal/Scopes.scala | 8 -------- src/reflect/scala/reflect/internal/Trees.scala | 4 ---- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index c05cecd49a33..febe7b346e0f 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -1645,7 +1645,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter) } private val hotCounters = - List(statistics.retainedCount, statistics.retainedByType, statistics.nodeByType) + List(statistics.retainedCount, statistics.retainedByType) private val parserStats = { import statistics.treeNodeCount if (settings.YhotStatisticsEnabled) treeNodeCount :: hotCounters diff --git a/src/reflect/scala/reflect/internal/Scopes.scala b/src/reflect/scala/reflect/internal/Scopes.scala index c7a58d59de80..927dca4f1526 100644 --- a/src/reflect/scala/reflect/internal/Scopes.scala +++ b/src/reflect/scala/reflect/internal/Scopes.scala @@ -316,7 +316,6 @@ trait Scopes extends api.Scopes { self: SymbolTable => * change to use iterators as too costly. */ def lookupEntry(name: Name): ScopeEntry = { - val startTime = if (StatisticsStatics.areSomeColdStatsEnabled) statistics.startTimer(statistics.scopeLookupTime) else null var e: ScopeEntry = null val flat = phase.flatClasses if (hashtable ne null) { @@ -330,7 +329,6 @@ trait Scopes extends api.Scopes { self: SymbolTable => e = e.next } } - if (StatisticsStatics.areSomeColdStatsEnabled) statistics.stopTimer(statistics.scopeLookupTime, startTime) e } @@ -481,22 +479,18 @@ trait Scopes extends api.Scopes { self: SymbolTable => /** Create a new scope nested in another one with which it shares its elements */ final def newNestedScope(outer: Scope): Scope = { - val startTime = if (StatisticsStatics.areSomeColdStatsEnabled) statistics.startTimer(statistics.scopePopulationTime) else null val nested = newScope // not `new Scope`, we must allow the runtime reflection universe to mixin SynchronizedScopes! nested.elems = outer.elems nested.nestinglevel = outer.nestinglevel + 1 if (outer.hashtable ne null) nested.hashtable = java.util.Arrays.copyOf(outer.hashtable, outer.hashtable.length) - if (StatisticsStatics.areSomeColdStatsEnabled) statistics.stopTimer(statistics.scopePopulationTime, startTime) nested } /** Create a new scope with given initial elements */ def newScopeWith(elems: Symbol*): Scope = { - val startTime = if (StatisticsStatics.areSomeColdStatsEnabled) statistics.startTimer(statistics.scopePopulationTime) else null val scope = newScope elems foreach scope.enter - if (StatisticsStatics.areSomeColdStatsEnabled) statistics.stopTimer(statistics.scopePopulationTime, startTime) scope } @@ -527,6 +521,4 @@ trait Scopes extends api.Scopes { self: SymbolTable => trait ScopeStats { self: Statistics => val scopeCountView = newView("#created scopes")(symbolTable.scopeCount) - val scopePopulationTime = newTimer("time spent in scope population") - val scopeLookupTime = newTimer("time spent in scope lookup") } diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index d6dd771922e1..a7cb9db9ef2c 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -46,9 +46,6 @@ trait Trees extends api.Trees { val id = nodeCount // TODO: add to attachment? nodeCount += 1 - if (StatisticsStatics.areSomeHotStatsEnabled()) - statistics.incCounter(statistics.nodeByType, getClass) - final override def pos: Position = rawatt.pos private[this] var rawtpe: Type = _ @@ -1955,7 +1952,6 @@ trait TreesStats { self: Statistics => val symbolTable: SymbolTable val treeNodeCount = newView("#created tree nodes")(symbolTable.nodeCount) - val nodeByType = newByClass("#created tree nodes by type")(newCounter("")) val retainedCount = newCounter("#retained tree nodes") val retainedByType = newByClass("#retained tree nodes by type")(newCounter("")) } From 330eba10b3766437d58585d48b3041923065a232 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 30 Jul 2019 12:07:33 +1000 Subject: [PATCH 4/7] Optimize BaseTypeSeq.exists --- src/reflect/scala/reflect/internal/BaseTypeSeqs.scala | 11 ++++++++++- .../scala/reflect/runtime/SynchronizedOps.scala | 1 - 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala b/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala index 6f92ef99d44c..2279b77f97d9 100644 --- a/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala +++ b/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala @@ -162,7 +162,16 @@ trait BaseTypeSeqs { def lateMap(f: Type => Type): BaseTypeSeq = newMappedBaseTypeSeq(this, f) - def exists(p: Type => Boolean): Boolean = elems exists p + @inline + final def exists(p: Type => Boolean): Boolean = { + var i = 0 + val es = elems + while (i < es.length) { + if (p(es(i))) return true + i += 1 + } + false + } lazy val maxDepth = maxDepthOfElems diff --git a/src/reflect/scala/reflect/runtime/SynchronizedOps.scala b/src/reflect/scala/reflect/runtime/SynchronizedOps.scala index 3ce1330008f5..4bedf191a7c2 100644 --- a/src/reflect/scala/reflect/runtime/SynchronizedOps.scala +++ b/src/reflect/scala/reflect/runtime/SynchronizedOps.scala @@ -43,7 +43,6 @@ private[reflect] trait SynchronizedOps extends internal.SymbolTable override def toList: List[Type] = gilSynchronized { super.toList } override def copy(head: Type, offset: Int): BaseTypeSeq = gilSynchronized { super.copy(head, offset) } override def map(f: Type => Type): BaseTypeSeq = gilSynchronized { super.map(f) } - override def exists(p: Type => Boolean): Boolean = gilSynchronized { super.exists(p) } override lazy val maxDepth = gilSynchronized { maxDepthOfElems } override def toString = gilSynchronized { super.toString } } From 7f69b81f7645105abc701cf8ff7b78bb3a0c252c Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 30 Jul 2019 12:17:19 +1000 Subject: [PATCH 5/7] BaseTypeSeq.exists --- src/reflect/scala/reflect/internal/BaseTypeSeqs.scala | 5 ++++- src/reflect/scala/reflect/internal/Types.scala | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala b/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala index 2279b77f97d9..3e36c3bffde6 100644 --- a/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala +++ b/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala @@ -52,15 +52,18 @@ trait BaseTypeSeqs { self => if (StatisticsStatics.areSomeColdStatsEnabled) statistics.incCounter(baseTypeSeqCount) if (StatisticsStatics.areSomeColdStatsEnabled) statistics.incCounter(baseTypeSeqLenTotal, elems.length) + private[this] var _hasAbstractTypeSymbol = false private[this] val typeSymbols = { val tmp = new Array[Int](elems.length) var i = 0 while (i < elems.length) { + hasAbstractTypeSymbol ||= elems(i).typeSymbolDirect.isAbstractType tmp(i) = elems(i).typeSymbol.id i += 1 } tmp } + def hasAbstractTypeSymbol: Boolean = _hasAbstractTypeSymbol /** The number of types in the sequence */ def length: Int = elems.length @@ -278,8 +281,8 @@ trait BaseTypeSeqs { override def copy(head: Type, offset: Int) = (orig map f).copy(head, offset) override def map(g: Type => Type) = lateMap(g) override def lateMap(g: Type => Type) = orig.lateMap(x => g(f(x))) - override def exists(p: Type => Boolean) = elems exists (x => p(f(x))) override protected def maxDepthOfElems: Depth = elems.map(x => typeDepth(f(x))).max + override def hasAbstractTypeSymbol: Boolean = elems exists (x => (f(x)).typeSymbolDirect.isAbstractType) override def toString = elems.mkString("MBTS(", ",", ")") } diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 8a7f3e3c6370..c4f0b8398456 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -2301,7 +2301,7 @@ trait Types override def decls: Scope = sym.info.decls protected[Types] def baseTypeSeqImpl: BaseTypeSeq = - if (sym.info.baseTypeSeq exists (_.typeSymbolDirect.isAbstractType)) + if (sym.info.baseTypeSeq.hasAbstractTypeSymbol) // scala/bug#8046 base type sequence might have more elements in a subclass, we can't map it element wise. relativize(sym.info).baseTypeSeq else From d964e75ba3d8d4f8916ae18656d81953880625b4 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 20 Feb 2019 17:15:36 +1000 Subject: [PATCH 6/7] Break up Symbol.info for JIT friendliness Also remove commented out debugging code and simplify adaptInfos. (cherry picked from commit 3bcabcbf7874d5d54efac06484c62c93742dd664) --- .../scala/reflect/internal/Symbols.scala | 173 +++++++++--------- 1 file changed, 85 insertions(+), 88 deletions(-) diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index a5e2488740f0..1a82a2b77ded 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -36,12 +36,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => protected def nextId() = { ids += 1; ids } - /** Used for deciding in the IDE whether we can interrupt the compiler */ - //protected var activeLocks = 0 - - /** Used for debugging only */ - //protected var lockedSyms = scala.collection.immutable.Set[Symbol]() - /** Used to keep track of the recursion depth on locked symbols */ private var _recursionTable = immutable.Map.empty[Symbol, Int] def recursionTable = _recursionTable @@ -587,16 +581,12 @@ trait Symbols extends api.Symbols { self: SymbolTable => } else { _rawflags |= LOCKED true -// activeLocks += 1 -// lockedSyms += this } } // Unlock a symbol private[scala] def unlock() = { if ((_rawflags & LOCKED) != 0L) { -// activeLocks -= 1 -// lockedSyms -= this _rawflags &= ~LOCKED if (settings.Yrecursion.value != 0) recursionTable -= this @@ -1517,41 +1507,42 @@ trait Symbols extends api.Symbols { self: SymbolTable => /** Get type info associated with symbol at current phase, after * ensuring that symbol is initialized (i.e. type is completed). */ - def info: Type = try { + def info: Type = { var cnt = 0 - while (validTo == NoPeriod) { - assert(infos ne null, this.name) - assert(infos.prev eq null, this.name) - val tp = infos.info - - if ((_rawflags & LOCKED) != 0L) { // rolled out once for performance - lock { - setInfo(ErrorType) - throw CyclicReference(this, tp) - } - } else { - _rawflags |= LOCKED - // TODO another commented out lines - this should be solved in one way or another -// activeLocks += 1 - // lockedSyms += this - } - val current = phase - try { - assertCorrectThread() - phase = phaseOf(infos.validFrom) - tp.complete(this) - } finally { - unlock() - phase = current - } + while (_validTo == NoPeriod) { + completeInfo() cnt += 1 // allow for two completions: // one: sourceCompleter to LazyType, two: LazyType to completed type - if (cnt == 3) abort(s"no progress in completing $this: $tp") + def abortNoProgress() = abort(s"no progress in completing $this: ${infos.info}") + if (cnt == 3) abortNoProgress() } rawInfo } - catch { + + private def completeInfo(): Unit = try { + assert(infos ne null, this.name) + assert(infos.prev eq null, this.name) + val tp = infos.info + + if ((_rawflags & LOCKED) != 0L) { // rolled out once for performance + lock { + setInfo(ErrorType) + throw CyclicReference(this, tp) + } + } else { + _rawflags |= LOCKED + } + val current = phase + try { + assertCorrectThread() + phase = phaseOf(infos.validFrom) + tp.complete(this) + } finally { + unlock() + phase = current + } + } catch { case ex: CyclicReference => devWarning("... hit cycle trying to complete " + this.fullLocationString) throw ex @@ -1607,81 +1598,87 @@ trait Symbols extends api.Symbols { self: SymbolTable => /** Return info without checking for initialization or completing */ def rawInfo: Type = { + // OPT: hoisting the outer reference reduces the bytecode size of this method a little which makes it more + // likely to inline into hot callers of .info + val outer = Symbols.this + var infos = this.infos - assert(infos != null) - val curPeriod = currentPeriod - val curPid = phaseId(curPeriod) + outer.assert(infos != null) + + if (_validTo != NoPeriod) { + val curPeriod = outer.currentPeriod + val curPid = outer.phaseId(curPeriod) - if (validTo != NoPeriod) { // skip any infos that concern later phases - while (curPid < phaseId(infos.validFrom) && infos.prev != null) + while (curPid < outer.phaseId(infos.validFrom) && infos.prev != null) infos = infos.prev - if (validTo < curPeriod) { - assertCorrectThread() + if (_validTo < curPeriod) { // adapt any infos that come from previous runs - val current = phase + val curPhase = outer.phase try { - infos = adaptInfos(infos) + if (infos != null && outer.runId(infos.validFrom) != outer.currentRunId) { + // scala/bug#8871 Discard all but the first element of type history. Specialization only works in the resident + // compiler / REPL if re-run its info transformer in this run to correctly populate its + // per-run caches, e.g. typeEnv + infos = adaptInfo(infos.oldest) + } //assert(runId(validTo) == currentRunId, name) //assert(runId(infos.validFrom) == currentRunId, name) - if (validTo < curPeriod) { - var itr = infoTransformers.nextFrom(phaseId(validTo)) - infoTransformers = itr; // caching optimization - while (itr.pid != NoPhase.id && itr.pid < current.id) { - phase = phaseWithId(itr.pid) - val info1 = itr.transform(this, infos.info) - if (info1 ne infos.info) { - infos = TypeHistory(currentPeriod + 1, info1, infos) - this.infos = infos - } - _validTo = currentPeriod + 1 // to enable reads from same symbol during info-transform - itr = itr.next - } - _validTo = if (itr.pid == NoPhase.id) curPeriod - else period(currentRunId, itr.pid) + if (_validTo < curPeriod) { + infos = transformInfos(infos, curPhase, curPeriod) } } finally { - phase = current + outer.phase = curPhase } } } infos.info } + private def transformInfos(infos0: TypeHistory, curPhase: Phase, curPeriod: Period): TypeHistory = { + assertCorrectThread() + var infos = infos0 + var itr = infoTransformers.nextFrom(phaseId(_validTo)) + infoTransformers = itr; // caching optimization + while (itr.pid != NoPhase.id && itr.pid < curPhase.id) { + phase = phaseWithId(itr.pid) + val info1 = itr.transform(this, infos.info) + if (info1 ne infos.info) { + infos = TypeHistory(currentPeriod + 1, info1, infos) + this.infos = infos + } + _validTo = currentPeriod + 1 // to enable reads from same symbol during info-transform + itr = itr.next + } + _validTo = if (itr.pid == NoPhase.id) curPeriod + else period(currentRunId, itr.pid) + infos + } + // adapt to new run in fsc. - private def adaptInfos(infos: TypeHistory): TypeHistory = { + private def adaptInfo(oldest: TypeHistory): TypeHistory = { assert(isCompilerUniverse) - if (infos == null || runId(infos.validFrom) == currentRunId) { - infos - } else if (infos ne infos.oldest) { - // scala/bug#8871 Discard all but the first element of type history. Specialization only works in the resident - // compiler / REPL if re-run its info transformer in this run to correctly populate its - // per-run caches, e.g. typeEnv - adaptInfos(infos.oldest) + assert(oldest.prev == null) + val pid = phaseId(oldest.validFrom) + + _validTo = period(currentRunId, pid) + phase = phaseWithId(pid) + + val info1 = adaptToNewRunMap(oldest.info) + if (info1 eq oldest.info) { + oldest.validFrom = validTo + this.infos = oldest + oldest } else { - val prev1 = adaptInfos(infos.prev) - if (prev1 ne infos.prev) prev1 - else { - val pid = phaseId(infos.validFrom) - - _validTo = period(currentRunId, pid) - phase = phaseWithId(pid) - - val info1 = adaptToNewRunMap(infos.info) - if (info1 eq infos.info) { - infos.validFrom = validTo - infos - } else { - this.infos = TypeHistory(validTo, info1, prev1) - this.infos - } - } + this.infos = TypeHistory(validTo, info1, null) + this.infos } } + /** Raises a `MissingRequirementError` if this symbol is a `StubSymbol` */ def failIfStub() {} From 4b1f854116a1614a7f0e12afe6c444f61e663ef7 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 30 Jul 2019 13:25:38 +1000 Subject: [PATCH 7/7] BaseTypeSeq.exists fixup --- src/reflect/scala/reflect/internal/BaseTypeSeqs.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala b/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala index 3e36c3bffde6..17663244c59c 100644 --- a/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala +++ b/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala @@ -57,7 +57,7 @@ trait BaseTypeSeqs { val tmp = new Array[Int](elems.length) var i = 0 while (i < elems.length) { - hasAbstractTypeSymbol ||= elems(i).typeSymbolDirect.isAbstractType + _hasAbstractTypeSymbol ||= elems(i).typeSymbolDirect.isAbstractType tmp(i) = elems(i).typeSymbol.id i += 1 }