Skip to content

Commit fe9b5be

Browse files
committed
Uniques: Replace HashSet by WeakHashSet
1 parent 1884125 commit fe9b5be

File tree

4 files changed

+75
-73
lines changed

4 files changed

+75
-73
lines changed

compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes {
4949
def newAnyRefMap[K <: AnyRef, V](): mutable.AnyRefMap[K, V] = new mutable.AnyRefMap[K, V]()
5050
def newWeakMap[K, V](): mutable.WeakHashMap[K, V] = new mutable.WeakHashMap[K, V]()
5151
def recordCache[T <: Clearable](cache: T): T = cache
52-
def newWeakSet[K >: Null <: AnyRef](): WeakHashSet[K] = new WeakHashSet[K]()
5352
def newMap[K, V](): mutable.HashMap[K, V] = new mutable.HashMap[K, V]()
5453
def newSet[K](): mutable.Set[K] = new mutable.HashSet[K]
5554
}
@@ -60,7 +59,6 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes {
6059
def newWeakMap[K, V](): collection.mutable.WeakHashMap[K, V]
6160
def newMap[K, V](): collection.mutable.HashMap[K, V]
6261
def newSet[K](): collection.mutable.Set[K]
63-
def newWeakSet[K >: Null <: AnyRef](): dotty.tools.dotc.util.WeakHashSet[K]
6462
def newAnyRefMap[K <: AnyRef, V](): collection.mutable.AnyRefMap[K, V]
6563
}
6664

compiler/src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ object Contexts {
559559
def platform: Platform = base.platform
560560
def pendingUnderlying: util.HashSet[Type] = base.pendingUnderlying
561561
def uniqueNamedTypes: Uniques.NamedTypeUniques = base.uniqueNamedTypes
562-
def uniques: util.HashSet[Type] = base.uniques
562+
def uniques: util.WeakHashSet[Type] = base.uniques
563563

564564
def initialize()(using Context): Unit = base.initialize()
565565
}

compiler/src/dotty/tools/dotc/core/Uniques.scala

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ package core
44
import Types._, Contexts._, util.Stats._, Hashable._, Names._
55
import config.Config
66
import Decorators._
7-
import util.{HashSet, Stats}
7+
import util.{WeakHashSet, Stats}
8+
import WeakHashSet.Entry
9+
import scala.annotation.tailrec
810

9-
class Uniques extends HashSet[Type](Config.initialUniquesCapacity):
11+
class Uniques extends WeakHashSet[Type](Config.initialUniquesCapacity):
1012
override def hash(x: Type): Int = x.hash
1113
override def isEqual(x: Type, y: Type) = x.eql(y)
1214

@@ -32,7 +34,7 @@ object Uniques:
3234
if tp.hash == NotCached then tp
3335
else ctx.uniques.put(tp).asInstanceOf[T]
3436

35-
final class NamedTypeUniques extends HashSet[NamedType](Config.initialUniquesCapacity * 4) with Hashable:
37+
final class NamedTypeUniques extends WeakHashSet[NamedType](Config.initialUniquesCapacity * 4) with Hashable:
3638
override def hash(x: NamedType): Int = x.hash
3739

3840
def enterIfNew(prefix: Type, designator: Designator, isTerm: Boolean)(using Context): NamedType =
@@ -42,33 +44,51 @@ object Uniques:
4244
if (isTerm) new CachedTermRef(prefix, designator, h)
4345
else new CachedTypeRef(prefix, designator, h)
4446
if h == NotCached then newType
45-
else
47+
else {
48+
// Inlined from WeakHashSet#put
4649
Stats.record(statsItem("put"))
47-
var idx = index(h)
48-
var e = entryAt(idx)
49-
while e != null do
50-
if (e.prefix eq prefix) && (e.designator eq designator) && (e.isTerm == isTerm) then return e
51-
idx = nextIndex(idx)
52-
e = entryAt(idx)
53-
addEntryAt(idx, newType)
50+
removeStaleEntries()
51+
val bucket = index(h)
52+
val oldHead = table(bucket)
53+
54+
@tailrec
55+
def linkedListLoop(entry: Entry[NamedType]): NamedType = entry match {
56+
case null => addEntryAt(bucket, newType, h, oldHead)
57+
case _ =>
58+
val e = entry.get
59+
if (e != null && (e.prefix eq prefix) && (e.designator eq designator) && (e.isTerm == isTerm)) e
60+
else linkedListLoop(entry.tail)
61+
}
62+
63+
linkedListLoop(oldHead)
64+
}
5465
end NamedTypeUniques
5566

56-
final class AppliedUniques extends HashSet[AppliedType](Config.initialUniquesCapacity * 2) with Hashable:
67+
final class AppliedUniques extends WeakHashSet[AppliedType](Config.initialUniquesCapacity * 2) with Hashable:
5768
override def hash(x: AppliedType): Int = x.hash
5869

5970
def enterIfNew(tycon: Type, args: List[Type]): AppliedType =
6071
val h = doHash(null, tycon, args)
6172
def newType = new CachedAppliedType(tycon, args, h)
6273
if monitored then recordCaching(h, classOf[CachedAppliedType])
6374
if h == NotCached then newType
64-
else
75+
else {
76+
// Inlined from WeakHashSet#put
6577
Stats.record(statsItem("put"))
66-
var idx = index(h)
67-
var e = entryAt(idx)
68-
while e != null do
69-
if (e.tycon eq tycon) && e.args.eqElements(args) then return e
70-
idx = nextIndex(idx)
71-
e = entryAt(idx)
72-
addEntryAt(idx, newType)
78+
removeStaleEntries()
79+
val bucket = index(h)
80+
val oldHead = table(bucket)
81+
82+
@tailrec
83+
def linkedListLoop(entry: Entry[AppliedType]): AppliedType = entry match {
84+
case null => addEntryAt(bucket, newType, h, oldHead)
85+
case _ =>
86+
val e = entry.get
87+
if (e != null && (e.tycon eq tycon) && e.args.eqElements(args)) e
88+
else linkedListLoop(entry.tail)
89+
}
90+
91+
linkedListLoop(oldHead)
92+
}
7393
end AppliedUniques
7494
end Uniques

compiler/src/dotty/tools/dotc/util/WeakHashSet.scala

Lines changed: 34 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,23 @@ import scala.collection.mutable
1717
* This set implementation is not in general thread safe without external concurrency control. However it behaves
1818
* properly when GC concurrently collects elements in this set.
1919
*/
20-
final class WeakHashSet[A <: AnyRef](initialCapacity: Int, loadFactor: Double) extends MutableSet[A] {
20+
abstract class WeakHashSet[A <: AnyRef](initialCapacity: Int = 16, loadFactor: Double = 0.75) extends MutableSet[A] {
2121

2222
import WeakHashSet._
2323

24-
def this() = this(initialCapacity = WeakHashSet.defaultInitialCapacity, loadFactor = WeakHashSet.defaultLoadFactor)
25-
2624
type This = WeakHashSet[A]
2725

2826
/**
2927
* queue of Entries that hold elements scheduled for GC
3028
* the removeStaleEntries() method works through the queue to remove
3129
* stale entries from the table
3230
*/
33-
private val queue = new ReferenceQueue[A]
31+
protected val queue = new ReferenceQueue[A]
3432

3533
/**
3634
* the number of elements in this set
3735
*/
38-
private var count = 0
36+
protected var count = 0
3937

4038
/**
4139
* from a specified initial capacity compute the capacity we'll use as being the next
@@ -52,33 +50,20 @@ final class WeakHashSet[A <: AnyRef](initialCapacity: Int, loadFactor: Double) e
5250
/**
5351
* the underlying table of entries which is an array of Entry linked lists
5452
*/
55-
private var table = new Array[Entry[A]](computeCapacity)
53+
protected var table = new Array[Entry[A]](computeCapacity)
5654

5755
/**
5856
* the limit at which we'll increase the size of the hash table
5957
*/
60-
private var threshold = computeThreshold
58+
protected var threshold = computeThreshold
6159

6260
private def computeThreshold: Int = (table.size * loadFactor).ceil.toInt
6361

64-
/**
65-
* find the bucket associated with an element's hash code
66-
*/
67-
private def bucketFor(hash: Int): Int = {
68-
// spread the bits around to try to avoid accidental collisions using the
69-
// same algorithm as java.util.HashMap
70-
var h = hash
71-
h ^= h >>> 20 ^ h >>> 12
72-
h ^= h >>> 7 ^ h >>> 4
73-
74-
// this is finding h % table.length, but takes advantage of the
75-
// fact that table length is a power of 2,
76-
// if you don't do bit flipping in your head, if table.length
77-
// is binary 100000.. (with n 0s) then table.length - 1
78-
// is 1111.. with n 1's.
79-
// In other words this masks on the last n bits in the hash
80-
h & (table.length - 1)
81-
}
62+
protected def hash(key: A): Int
63+
protected def isEqual(x: A, y: A): Boolean = x.equals(y)
64+
65+
/** Turn hashcode `x` into a table index */
66+
protected def index(x: Int): Int = x & (table.length - 1)
8267

8368
/**
8469
* remove a single entry from a linked list in a given bucket
@@ -94,14 +79,14 @@ final class WeakHashSet[A <: AnyRef](initialCapacity: Int, loadFactor: Double) e
9479
/**
9580
* remove entries associated with elements that have been gc'ed
9681
*/
97-
private def removeStaleEntries(): Unit = {
82+
protected def removeStaleEntries(): Unit = {
9883
def poll(): Entry[A] = queue.poll().asInstanceOf[Entry[A]]
9984

10085
@tailrec
10186
def queueLoop(): Unit = {
10287
val stale = poll()
10388
if (stale != null) {
104-
val bucket = bucketFor(stale.hash)
89+
val bucket = index(stale.hash)
10590

10691
@tailrec
10792
def linkedListLoop(prevEntry: Entry[A], entry: Entry[A]): Unit = if (stale eq entry) remove(bucket, prevEntry, entry)
@@ -119,7 +104,7 @@ final class WeakHashSet[A <: AnyRef](initialCapacity: Int, loadFactor: Double) e
119104
/**
120105
* Double the size of the internal table
121106
*/
122-
private def resize(): Unit = {
107+
protected def resize(): Unit = {
123108
val oldTable = table
124109
table = new Array[Entry[A]](oldTable.size * 2)
125110
threshold = computeThreshold
@@ -130,7 +115,7 @@ final class WeakHashSet[A <: AnyRef](initialCapacity: Int, loadFactor: Double) e
130115
def linkedListLoop(entry: Entry[A]): Unit = entry match {
131116
case null => ()
132117
case _ =>
133-
val bucket = bucketFor(entry.hash)
118+
val bucket = index(entry.hash)
134119
val oldNext = entry.tail
135120
entry.tail = table(bucket)
136121
table(bucket) = entry
@@ -147,8 +132,7 @@ final class WeakHashSet[A <: AnyRef](initialCapacity: Int, loadFactor: Double) e
147132
case null => throw new NullPointerException("WeakHashSet cannot hold nulls")
148133
case _ =>
149134
removeStaleEntries()
150-
val hash = elem.hashCode
151-
val bucket = bucketFor(hash)
135+
val bucket = index(hash(elem))
152136

153137
@tailrec
154138
def linkedListLoop(entry: Entry[A]): A = entry match {
@@ -162,24 +146,24 @@ final class WeakHashSet[A <: AnyRef](initialCapacity: Int, loadFactor: Double) e
162146
linkedListLoop(table(bucket))
163147
}
164148

149+
protected def addEntryAt(bucket: Int, elem: A, elemHash: Int, oldHead: Entry[A]): A = {
150+
table(bucket) = new Entry(elem, elemHash, oldHead, queue)
151+
count += 1
152+
if (count > threshold) resize()
153+
elem
154+
}
155+
165156
def put(elem: A): A = elem match {
166157
case null => throw new NullPointerException("WeakHashSet cannot hold nulls")
167158
case _ =>
168159
removeStaleEntries()
169-
val hash = elem.hashCode
170-
val bucket = bucketFor(hash)
160+
val h = hash(elem)
161+
val bucket = index(h)
171162
val oldHead = table(bucket)
172163

173-
def add() = {
174-
table(bucket) = new Entry(elem, hash, oldHead, queue)
175-
count += 1
176-
if (count > threshold) resize()
177-
elem
178-
}
179-
180164
@tailrec
181165
def linkedListLoop(entry: Entry[A]): A = entry match {
182-
case null => add()
166+
case null => addEntryAt(bucket, elem, h, oldHead)
183167
case _ =>
184168
val entryElem = entry.get
185169
if (isEqual(elem, entryElem)) entryElem
@@ -195,7 +179,7 @@ final class WeakHashSet[A <: AnyRef](initialCapacity: Int, loadFactor: Double) e
195179
case null =>
196180
case _ =>
197181
removeStaleEntries()
198-
val bucket = bucketFor(elem.hashCode)
182+
val bucket = index(hash(elem))
199183

200184

201185

@@ -276,6 +260,11 @@ final class WeakHashSet[A <: AnyRef](initialCapacity: Int, loadFactor: Double) e
276260
}
277261
}
278262

263+
protected def statsItem(op: String) =
264+
val prefix = "WeakHashSet"
265+
val suffix = getClass.getSimpleName
266+
s"$prefix$op $suffix"
267+
279268
/**
280269
* Diagnostic information about the internals of this set. Not normally
281270
* needed by ordinary code, but may be useful for diagnosing performance problems
@@ -296,9 +285,9 @@ final class WeakHashSet[A <: AnyRef](initialCapacity: Int, loadFactor: Double) e
296285
assert(entry.get != null, s"$entry had a null value indicated that gc activity was happening during diagnostic validation or that a null value was inserted")
297286
computedCount += 1
298287
val cachedHash = entry.hash
299-
val realHash = entry.get.hashCode
288+
val realHash = hash(entry.get)
300289
assert(cachedHash == realHash, s"for $entry cached hash was $cachedHash but should have been $realHash")
301-
val computedBucket = bucketFor(realHash)
290+
val computedBucket = index(realHash)
302291
assert(computedBucket == bucket, s"for $entry the computed bucket was $computedBucket but should have been $bucket")
303292

304293
entry = entry.tail
@@ -344,11 +333,6 @@ object WeakHashSet {
344333
* A single entry in a WeakHashSet. It's a WeakReference plus a cached hash code and
345334
* a link to the next Entry in the same bucket
346335
*/
347-
private class Entry[A](@constructorOnly element: A, val hash:Int, var tail: Entry[A], @constructorOnly queue: ReferenceQueue[A]) extends WeakReference[A](element, queue)
348-
349-
private final val defaultInitialCapacity = 16
350-
private final val defaultLoadFactor = .75
336+
class Entry[A](@constructorOnly element: A, val hash:Int, var tail: Entry[A], @constructorOnly queue: ReferenceQueue[A]) extends WeakReference[A](element, queue)
351337

352-
def apply[A <: AnyRef](initialCapacity: Int = defaultInitialCapacity, loadFactor: Double = defaultLoadFactor): WeakHashSet[A] =
353-
new WeakHashSet(initialCapacity, loadFactor)
354338
}

0 commit comments

Comments
 (0)