Skip to content

Commit acc860e

Browse files
committed
Implement NameTable in terms of HashSet
Reduce synchronized region to updates.
1 parent 16f795a commit acc860e

File tree

1 file changed

+59
-76
lines changed

1 file changed

+59
-76
lines changed

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

Lines changed: 59 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import StdNames.str
1010
import scala.internal.Chars.isIdentifierStart
1111
import collection.immutable
1212
import config.Config
13-
import util.LinearMap
13+
import util.{LinearMap, HashSet}
1414

1515
import scala.annotation.internal.sharable
1616

@@ -262,10 +262,9 @@ object Names {
262262
}
263263

264264
/** A simple name is essentially an interned string */
265-
final class SimpleName(val start: Int, val length: Int, @sharable private[Names] var next: SimpleName) extends TermName {
266-
// `next` is @sharable because it is only modified in the synchronized block of termName.
265+
final class SimpleName(val start: Int, val length: Int) extends TermName {
267266

268-
/** The n'th character */
267+
/** The n'th character */
269268
def apply(n: Int): Char = chrs(start + n)
270269

271270
/** A character in this name satisfies predicate `p` */
@@ -499,27 +498,70 @@ object Names {
499498
override def debugString: String = s"${underlying.debugString}[$info]"
500499
}
501500

501+
/** The term name represented by the empty string */
502+
val EmptyTermName: SimpleName = SimpleName(-1, 0)
503+
502504
// Nametable
503505

504-
private final val InitialHashSize = 0x8000
505-
private final val InitialNameSize = 0x20000
506-
private final val fillFactor = 0.7
506+
inline val InitialNameSize = 0x20000
507507

508508
/** Memory to store all names sequentially. */
509-
@sharable // because it's only mutated in synchronized block of termName
509+
@sharable // because it's only mutated in synchronized block of enterIfNew
510510
private[dotty] var chrs: Array[Char] = new Array[Char](InitialNameSize)
511511

512512
/** The number of characters filled. */
513-
@sharable // because it's only mutated in synchronized block of termName
513+
@sharable // because it's only mutated in synchronized block of enterIfNew
514514
private var nc = 0
515515

516-
/** Hashtable for finding term names quickly. */
517-
@sharable // because it's only mutated in synchronized block of termName
518-
private var table = new Array[SimpleName](InitialHashSize)
516+
/** Make sure the capacity of the character array is at least `n` */
517+
private def ensureCapacity(n: Int) =
518+
if n > chrs.length then
519+
val newchrs = new Array[Char](chrs.length * 2)
520+
chrs.copyToArray(newchrs)
521+
chrs = newchrs
522+
523+
private class NameTable extends HashSet[SimpleName](initialCapacity = 0x10000, capacityMultiple = 2):
524+
import util.Stats
525+
526+
override def hash(x: SimpleName) = hashValue(chrs, x.start, x.length) // needed for resize
527+
override def isEqual(x: SimpleName, y: SimpleName) = ??? // not needed
528+
529+
def enterIfNew(cs: Array[Char], offset: Int, len: Int): SimpleName =
530+
Stats.record(statsItem("put"))
531+
val table = currentTable
532+
var idx = hashValue(cs, offset, len) & (table.length - 1)
533+
var name = table(idx).asInstanceOf[SimpleName]
534+
while name != null do
535+
if name.length == len && Names.equals(name.start, cs, offset, len) then
536+
return name
537+
Stats.record(statsItem("miss"))
538+
idx = (idx + 1) & (table.length - 1)
539+
name = table(idx).asInstanceOf[SimpleName]
540+
Stats.record(statsItem("addEntryAt"))
541+
synchronized {
542+
if (table eq currentTable) && table(idx) == null then
543+
// Our previous unsynchronized computation of the next free index is still correct.
544+
// This relies on the fact that table entries go from null to non-null, and then
545+
// stay the same. Note that we do not need the table or the entry in it to be
546+
// volatile since SimpleNames are immutable, and hence safely published.
547+
// The same holds for the chrs array. We might miss before the synchronized
548+
// on published characters but that would make name comparison false, which
549+
// means we end up in the synchronized block here, where we get the correct state
550+
name = SimpleName(nc, len)
551+
ensureCapacity(nc + len)
552+
Array.copy(cs, offset, chrs, nc, len)
553+
nc += len
554+
addEntryAt(idx, name)
555+
else
556+
enterIfNew(cs, offset, len)
557+
}
558+
559+
addEntryAt(0, EmptyTermName)
560+
end NameTable
519561

520-
/** The number of defined names. */
521-
@sharable // because it's only mutated in synchronized block of termName
522-
private var size = 1
562+
/** Hashtable for finding term names quickly. */
563+
@sharable // because it's only mutated in synchronized block of enterIfNew
564+
private val nameTable = NameTable()
523565

524566
/** The hash of a name made of from characters cs[offset..offset+len-1]. */
525567
private def hashValue(cs: Array[Char], offset: Int, len: Int): Int = {
@@ -545,62 +587,8 @@ object Names {
545587
/** Create a term name from the characters in cs[offset..offset+len-1].
546588
* Assume they are already encoded.
547589
*/
548-
def termName(cs: Array[Char], offset: Int, len: Int): SimpleName = synchronized {
549-
util.Stats.record("termName")
550-
val h = hashValue(cs, offset, len) & (table.length - 1)
551-
552-
/** Make sure the capacity of the character array is at least `n` */
553-
def ensureCapacity(n: Int) =
554-
if (n > chrs.length) {
555-
val newchrs = new Array[Char](chrs.length * 2)
556-
chrs.copyToArray(newchrs)
557-
chrs = newchrs
558-
}
559-
560-
/** Enter characters into chrs array. */
561-
def enterChars(): Unit = {
562-
ensureCapacity(nc + len)
563-
var i = 0
564-
while (i < len) {
565-
chrs(nc + i) = cs(offset + i)
566-
i += 1
567-
}
568-
nc += len
569-
}
570-
571-
/** Rehash chain of names */
572-
def rehash(name: SimpleName): Unit =
573-
if (name != null) {
574-
val oldNext = name.next
575-
val h = hashValue(chrs, name.start, name.length) & (table.size - 1)
576-
name.next = table(h)
577-
table(h) = name
578-
rehash(oldNext)
579-
}
580-
581-
/** Make sure the hash table is large enough for the given load factor */
582-
def incTableSize() = {
583-
size += 1
584-
if (size.toDouble / table.size > fillFactor) {
585-
val oldTable = table
586-
table = new Array[SimpleName](table.size * 2)
587-
for (i <- 0 until oldTable.size) rehash(oldTable(i))
588-
}
589-
}
590-
591-
val next = table(h)
592-
var name = next
593-
while (name ne null) {
594-
if (name.length == len && equals(name.start, cs, offset, len))
595-
return name
596-
name = name.next
597-
}
598-
name = new SimpleName(nc, len, next)
599-
enterChars()
600-
table(h) = name
601-
incTableSize()
602-
name
603-
}
590+
def termName(cs: Array[Char], offset: Int, len: Int): SimpleName =
591+
nameTable.enterIfNew(cs, offset, len)
604592

605593
/** Create a type name from the characters in cs[offset..offset+len-1].
606594
* Assume they are already encoded.
@@ -631,11 +619,6 @@ object Names {
631619
/** Create a type name from a string */
632620
def typeName(s: String): TypeName = typeName(s.toCharArray, 0, s.length)
633621

634-
table(0) = new SimpleName(-1, 0, null)
635-
636-
/** The term name represented by the empty string */
637-
val EmptyTermName: TermName = table(0)
638-
639622
/** The type name represented by the empty string */
640623
val EmptyTypeName: TypeName = EmptyTermName.toTypeName
641624

0 commit comments

Comments
 (0)