Skip to content

Commit 6f632b4

Browse files
authored
Merge pull request #9865 from dotty-staging/fix-sets-maps
Some improvements to util.HashSet and HashMap
2 parents 4cb9ee1 + daf6db7 commit 6f632b4

File tree

8 files changed

+136
-21
lines changed

8 files changed

+136
-21
lines changed

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -845,8 +845,6 @@ class Definitions {
845845
@tu lazy val Not_value: Symbol = NotClass.companionModule.requiredMethod(nme.value)
846846

847847
@tu lazy val ValueOfClass: ClassSymbol = requiredClass("scala.ValueOf")
848-
@tu lazy val StatsModule: Symbol = requiredModule("dotty.tools.dotc.util.Stats")
849-
@tu lazy val Stats_doRecord: Symbol = StatsModule.requiredMethod("doRecord")
850848

851849
@tu lazy val FromDigitsClass: ClassSymbol = requiredClass("scala.util.FromDigits")
852850
@tu lazy val FromDigits_WithRadixClass: ClassSymbol = requiredClass("scala.util.FromDigits.WithRadix")

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ object NameKinds {
124124
case class QualInfo(name: SimpleName) extends Info with QualifiedInfo {
125125
override def map(f: SimpleName => SimpleName): NameInfo = new QualInfo(f(name))
126126
override def toString: String = s"$infoString $name"
127+
override def hashCode = scala.runtime.ScalaRunTime._hashCode(this) * 31 + kind.hashCode
127128
}
128129

129130
def apply(qual: TermName, name: SimpleName): TermName =
@@ -173,6 +174,7 @@ object NameKinds {
173174
type ThisInfo = NumberedInfo
174175
case class NumberedInfo(val num: Int) extends Info with NameKinds.NumberedInfo {
175176
override def toString: String = s"$infoString $num"
177+
override def hashCode = scala.runtime.ScalaRunTime._hashCode(this) * 31 + kind.hashCode
176178
}
177179
def apply(qual: TermName, num: Int): TermName =
178180
qual.derived(new NumberedInfo(num))
@@ -371,6 +373,7 @@ object NameKinds {
371373
case class SignedInfo(sig: Signature) extends Info {
372374
assert(sig ne Signature.NotAMethod)
373375
override def toString: String = s"$infoString $sig"
376+
override def hashCode = scala.runtime.ScalaRunTime._hashCode(this) * 31 + kind.hashCode
374377
}
375378
type ThisInfo = SignedInfo
376379

compiler/src/dotty/tools/dotc/transform/Instrumentation.scala

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package dotty.tools.dotc
1+
package dotty.tools
2+
package dotc
23
package transform
34

45
import core._
@@ -27,33 +28,57 @@ class Instrumentation extends MiniPhase { thisPhase =>
2728
override def isEnabled(using Context) =
2829
ctx.settings.Yinstrument.value
2930

30-
private val namesOfInterest = List(
31-
"::", "+=", "toString", "newArray", "box", "toCharArray",
32-
"map", "flatMap", "filter", "withFilter", "collect", "foldLeft", "foldRight", "take",
33-
"reverse", "mapConserve", "mapconserve", "filterConserve", "zip",
34-
"denotsNamed", "lookup", "lookupEntry", "lookupAll", "toList")
35-
private var namesToRecord: Set[Name] = _
31+
private val collectionNamesOfInterest = List(
32+
"map", "flatMap", "filter", "filterNot", "withFilter", "collect", "flatten", "foldLeft", "foldRight", "take",
33+
"reverse", "zip", "++", ":::", ":+", "distinct", "dropRight", "takeRight", "groupBy", "groupMap", "init", "inits",
34+
"interect", "mkString", "partition", "reverse_:::", "scanLeft", "scanRight",
35+
"sortBy", "sortWith", "sorted", "span", "splitAt", "takeWhile", "transpose", "unzip", "unzip3",
36+
"updated", "zipAll", "zipWithIndex",
37+
"mapConserve", "mapconserve", "filterConserve", "zipWithConserve", "mapWithIndexConserve"
38+
)
39+
40+
private val namesOfInterest = collectionNamesOfInterest ++ List(
41+
"::", "+=", "toString", "newArray", "box", "toCharArray", "termName", "typeName",
42+
"slice", "staticRef", "requiredClass")
3643

37-
private var consName: TermName = _
38-
private var consEqName: TermName = _
44+
private var namesToRecord: Set[Name] = _
45+
private var collectionNamesToRecord: Set[Name] = _
46+
private var Stats_doRecord: Symbol = _
47+
private var Stats_doRecordSize: Symbol = _
48+
private var CollectionIterableClass: ClassSymbol = _
3949

4050
override def prepareForUnit(tree: Tree)(using Context): Context =
4151
namesToRecord = namesOfInterest.map(_.toTermName).toSet
52+
collectionNamesToRecord = collectionNamesOfInterest.map(_.toTermName).toSet
53+
val StatsModule = requiredModule("dotty.tools.dotc.util.Stats")
54+
Stats_doRecord = StatsModule.requiredMethod("doRecord")
55+
Stats_doRecordSize = StatsModule.requiredMethod("doRecordSize")
56+
CollectionIterableClass = requiredClass("scala.collection.Iterable")
4257
ctx
4358

4459
private def record(category: String, tree: Tree)(using Context): Tree = {
4560
val key = Literal(Constant(s"$category@${tree.sourcePos.show}"))
46-
ref(defn.Stats_doRecord).appliedTo(key, Literal(Constant(1)))
61+
ref(Stats_doRecord).appliedTo(key, Literal(Constant(1)))
4762
}
4863

64+
private def recordSize(tree: Apply)(using Context): Tree = tree.fun match
65+
case sel @ Select(qual, name)
66+
if collectionNamesToRecord.contains(name)
67+
&& qual.tpe.widen.derivesFrom(CollectionIterableClass) =>
68+
val key = Literal(Constant(s"totalSize/${name} in ${qual.tpe.widen.classSymbol.name}@${tree.sourcePos.show}"))
69+
val qual1 = ref(Stats_doRecordSize).appliedTo(key, qual).cast(qual.tpe.widen)
70+
cpy.Apply(tree)(cpy.Select(sel)(qual1, name), tree.args)
71+
case _ =>
72+
tree
73+
4974
private def ok(using Context) =
5075
!ctx.owner.ownersIterator.exists(_.name.toString.startsWith("Stats"))
5176

5277
override def transformApply(tree: Apply)(using Context): Tree = tree.fun match {
5378
case Select(nu: New, _) =>
5479
cpy.Block(tree)(record(i"alloc/${nu.tpe}", tree) :: Nil, tree)
5580
case ref: RefTree if namesToRecord.contains(ref.name) && ok =>
56-
cpy.Block(tree)(record(i"call/${ref.name}", tree) :: Nil, tree)
81+
cpy.Block(tree)(record(i"call/${ref.name}", tree) :: Nil, recordSize(tree))
5782
case _ =>
5883
tree
5984
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,11 @@ abstract class GenericHashMap[Key, Value]
111111
k = keyAt(idx)
112112
k != null
113113
do
114+
val eidx = index(hash(k))
114115
if isDense
115-
|| index(hole - index(hash(k))) < limit * 2
116-
// hash(k) is then logically at or before hole; can be moved forward to fill hole
116+
|| index(eidx - (hole + 2)) > index(idx - (hole + 2))
117+
// entry `e` at `idx` can move unless `index(hash(e))` is in
118+
// the (ring-)interval [hole + 2 .. idx]
117119
then
118120
setKey(hole, k)
119121
setValue(hole, valueAt(idx))
@@ -156,7 +158,7 @@ abstract class GenericHashMap[Key, Value]
156158
protected def growTable(): Unit =
157159
val oldTable = table
158160
val newLength =
159-
if oldTable.length == DenseLimit then DenseLimit * 2 * roundToPower(capacityMultiple)
161+
if table.length == DenseLimit * 2 then table.length * roundToPower(capacityMultiple)
160162
else table.length
161163
allocate(newLength)
162164
copyFrom(oldTable)

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ extends GenericHashMap[Key, Value](initialCapacity, capacityMultiple):
1111
/** Hashcode is left-shifted by 1, so lowest bit is not lost
1212
* when taking the index.
1313
*/
14-
final def hash(x: Key): Int = x.hashCode << 1
14+
final def hash(key: Key): Int =
15+
val h = key.hashCode
16+
// Part of the MurmurHash3 32 bit finalizer
17+
val i = (h ^ (h >>> 16)) * 0x85EBCA6B
18+
val j = (i ^ (i >>> 13)) & 0x7FFFFFFF
19+
(if j==0 then 0x41081989 else j) << 1
1520

1621
final def isEqual(x: Key, y: Key): Boolean = x.equals(y)
1722

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,13 @@ class HashSet[T](initialCapacity: Int = 8, capacityMultiple: Int = 2) extends Mu
4848

4949
protected def isDense = limit < DenseLimit
5050

51-
/** Hashcode, by defualt `x.hashCode`, can be overridden */
52-
protected def hash(x: T): Int = x.hashCode
51+
/** Hashcode, by default a processed `x.hashCode`, can be overridden */
52+
protected def hash(key: T): Int =
53+
val h = key.hashCode
54+
// Part of the MurmurHash3 32 bit finalizer
55+
val i = (h ^ (h >>> 16)) * 0x85EBCA6B
56+
val j = (i ^ (i >>> 13)) & 0x7FFFFFFF
57+
if j==0 then 0x41081989 else j
5358

5459
/** Hashcode, by default `equals`, can be overridden */
5560
protected def isEqual(x: T, y: T): Boolean = x.equals(y)
@@ -109,9 +114,11 @@ class HashSet[T](initialCapacity: Int = 8, capacityMultiple: Int = 2) extends Mu
109114
e = entryAt(idx)
110115
e != null
111116
do
117+
val eidx = index(hash(e))
112118
if isDense
113-
|| index(hole - index(hash(e))) < limit
114-
// hash(k) is then logically at or before hole; can be moved forward to fill hole
119+
|| index(eidx - (hole + 1)) > index(idx - (hole + 1))
120+
// entry `e` at `idx` can move unless `index(hash(e))` is in
121+
// the (ring-)interval [hole + 1 .. idx]
115122
then
116123
setEntry(hole, e)
117124
hole = idx

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ import collection.mutable
2828
hits(name) += n
2929
}
3030

31+
def doRecordSize(fn: String, coll: scala.collection.Iterable[_]): coll.type =
32+
doRecord(fn, coll.size)
33+
coll
34+
3135
inline def trackTime[T](fn: String)(inline op: T): T =
3236
if (enabled) doTrackTime(fn)(op) else op
3337

tests/run-with-compiler/settest.scala

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
trait Generator[+T]:
2+
self =>
3+
def generate: T
4+
def map[S](f: T => S) = new Generator[S]:
5+
def generate: S = f(self.generate)
6+
def flatMap[S](f: T => Generator[S]) = new Generator[S]:
7+
def generate: S = f(self.generate).generate
8+
9+
object Generator:
10+
val NumLimit = 300
11+
val Iterations = 10000
12+
13+
given integers as Generator[Int]:
14+
val rand = new java.util.Random
15+
def generate = rand.nextInt()
16+
17+
given booleans as Generator[Boolean] =
18+
integers.map(x => x > 0)
19+
20+
def range(end: Int): Generator[Int] =
21+
integers.map(x => (x % end).abs)
22+
23+
enum Op:
24+
case Lookup, Update, Remove
25+
export Op._
26+
27+
given ops as Generator[Op] =
28+
range(10).map {
29+
case 0 | 1 | 2 | 3 => Lookup
30+
case 4 | 5 | 6 | 7 => Update
31+
case 8 | 9 => Remove
32+
}
33+
34+
val nums: Generator[Integer] = range(NumLimit).map(Integer(_))
35+
36+
@main def Test =
37+
import Generator._
38+
39+
val set1 = dotty.tools.dotc.util.HashSet[Int]()
40+
val set2 = scala.collection.mutable.HashSet[Int]()
41+
42+
def checkSame() =
43+
assert(set1.size == set2.size)
44+
for e <- set1.iterator do
45+
assert(set2.contains(e))
46+
for e <- set2.iterator do
47+
assert(set1.contains(e))
48+
49+
def lookupTest(num: Integer) =
50+
val res1 = set1.contains(num)
51+
val res2 = set2.contains(num)
52+
assert(res1 == res2)
53+
54+
def updateTest(num: Integer) =
55+
lookupTest(num)
56+
set1 += num
57+
set2 += num
58+
checkSame()
59+
60+
def removeTest(num: Integer) =
61+
//println(s"test remove $num")
62+
set1 -= num
63+
set2 -= num
64+
checkSame()
65+
66+
for i <- 0 until Iterations do
67+
val num = nums.generate
68+
Generator.ops.generate match
69+
case Lookup => lookupTest(num)
70+
case Update => updateTest(num)
71+
case Remove => removeTest(num)

0 commit comments

Comments
 (0)