Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/cc/CaptureSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,7 @@ object CaptureSet:
override def optionalInfo(using Context): String =
for vars <- ctx.property(ShownVars) do vars += this
if !ctx.settings.YccDebug.value then ""
else if isConst then ids ++ "(solved)"
else if isConst then ids + "(solved)"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmarking and profiling this PR turned into a general "let me look at what String-related stuff pops up" exercise.

We can't inline StringOps because the compiler might run with a different standard library.

Also, IMHO, it's weird to use ++ on strings when we're not explicitly looking at them as sequences of characters.

else ids

/** Used for diagnostics and debugging: A string that traces the creation
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/cc/SepCheck.scala
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,7 @@ object SepCheck:
var directPeaks : Refs = emptyRefs

private def double[T <: AnyRef : ClassTag](xs: Array[T]): Array[T] =
val xs1 = new Array[T](xs.length * 2)
xs.copyToArray(xs1)
xs1
Array.copyOf(xs, xs.length * 2)

private def ensureCapacity(added: Int): Unit =
if size + added > refs.length then
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,9 @@ object NameOps {
}
}

/** Do two target names match? An empty target name matchws any other name. */
/** Do two target names match? An empty target name matches any other name. */
def matchesTargetName(other: Name) =
name == other || name.isEmpty || other.isEmpty
name.isEmpty || other.isEmpty || name == other

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor but alongside the other targetName change, noticed while profiling, this order seems to make more sense to avoid calling into string equality if we can


private def functionSuffixStart: Int =
val first = name.firstPart
Expand Down
98 changes: 51 additions & 47 deletions compiler/src/dotty/tools/dotc/core/Names.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ package core

import scala.io.Codec
import util.NameTransformer
import printing.{Showable, Texts, Printer}
import printing.{Printer, Showable, Texts}
import Texts.Text
import StdNames.str
import config.Config
import util.{LinearMap, HashSet}
import util.{HashSet, LinearMap}

import java.nio.CharBuffer
import scala.annotation.internal.sharable

object Names {
Expand Down Expand Up @@ -325,20 +325,13 @@ object Names {
def head: Char = apply(0)
def last: Char = apply(length - 1)

/** Copy character slice (from until end) to character array starting at `dstStart`.
* @pre Destination must have enough space to hold all characters of this name.
*/
def getChars(from: Int, end: Int, dst: Array[Char], dstStart: Int): Unit =
assert(0 <= from && from <= end && end <= length)
Array.copy(chrs, start + from, dst, dstStart, end - from)

def toUTF8Bytes(): Array[Byte] =
if length == 0 then Array.emptyByteArray
else Codec.toUTF8(chrs, start, length)

override def asSimpleName: SimpleName = this
override def toSimpleName: SimpleName = this
override final def mangle: SimpleName = encode
override def mangle: SimpleName = encode

override def replace(f: PartialFunction[Name, Name]): ThisName =
if (f.isDefinedAt(this)) likeSpaced(f(this)) else this
Expand Down Expand Up @@ -501,23 +494,21 @@ object Names {
/** Make sure the capacity of the character array is at least `n` */
private def ensureCapacity(n: Int) =
if n > chrs.length then
val newchrs = new Array[Char](chrs.length * 2)
chrs.copyToArray(newchrs)
chrs = newchrs
chrs = Array.copyOf(chrs, chrs.length * 2)

private class NameTable extends HashSet[SimpleName](initialCapacity = 0x10000, capacityMultiple = 2):
private final class NameTable extends HashSet[SimpleName](initialCapacity = 0x10000, capacityMultiple = 2):
import util.Stats

override def hash(x: SimpleName) = hashValue(chrs, x.start, x.length) // needed for resize
override def isEqual(x: SimpleName, y: SimpleName) = ??? // not needed

def enterIfNew(cs: Array[Char], offset: Int, len: Int): SimpleName =
def enterIfNew(cs: CharSequence): SimpleName =
Stats.record(statsItem("put"))
val myTable = currentTable // could be outdated under parallel execution
var idx = hashValue(cs, offset, len) & (myTable.length - 1)
var idx = hashValue(cs) & (myTable.length - 1)
var name: SimpleName | Null = myTable(idx).asInstanceOf[SimpleName | Null]
while name != null do
if name.nn.length == len && Names.equals(name.nn.start, cs, offset, len) then
if Names.equals(name.nn.start, name.nn.length, cs) then
return name.nn
Stats.record(statsItem("miss"))
idx = (idx + 1) & (myTable.length - 1)
Expand All @@ -532,13 +523,13 @@ object Names {
// The same holds for the chrs array. We might miss before the synchronized
// on published characters but that would make name comparison false, which
// means we end up in the synchronized block here, where we get the correct state.
name = SimpleName(nc, len)
ensureCapacity(nc + len)
Array.copy(cs, offset, chrs, nc, len)
nc += len
name = SimpleName(nc, cs.length())
ensureCapacity(nc + cs.length())
copyTo(cs, chrs, nc)
nc += cs.length()
addEntryAt(idx, name.nn)
else
enterIfNew(cs, offset, len)
enterIfNew(cs)
}

addEntryAt(0, EmptyTermName: @unchecked)
Expand All @@ -548,7 +539,14 @@ object Names {
@sharable // because it's only mutated in synchronized block of enterIfNew
private val nameTable = NameTable()

/** The hash of a name made of from characters cs[offset..offset+len-1]. */
/** Copies the given character sequence to the given array starting at the given destination index. */
private def copyTo(cs: CharSequence, dst: Array[Char], dstBegin: Int): Unit =
var i = 0
while i < cs.length() do
dst(i + dstBegin) = cs.charAt(i)
i += 1

/** The hash of a name made of from characters cs[offset..offset+len-1]. Same algorithm as java.lang.String. */
private def hashValue(cs: Array[Char], offset: Int, len: Int): Int = {
var i = offset
var hash = 0
Expand All @@ -559,50 +557,56 @@ object Names {
hash
}

/** Is (the ASCII representation of) name at given index equal to
* cs[offset..offset+len-1]?
*/
private def equals(index: Int, cs: Array[Char], offset: Int, len: Int): Boolean = {
/** The hash of the given character sequence, using the same algorithm as above. */
private def hashValue(cs: CharSequence): Int = cs match {
case s: String => s.hashCode
case _ =>
var i = 0
var hash = 0
val len = cs.length()
while (i < len) {
hash = 31 * hash + cs.charAt(i)
i += 1
}
hash
}

/** Is (the ASCII representation of) name at given index equal to cs? */
private def equals(index: Int, length: Int, cs: CharSequence): Boolean = {
var i = 0
while ((i < len) && (chrs(index + i) == cs(offset + i)))
val len = cs.length()
if len != length then
return false
while i < len && chrs(index + i) == cs.charAt(i) do
i += 1
i == len
}

/** Create a term name from the characters in cs[offset..offset+len-1].
* Assume they are already encoded.
*/
def termName(cs: Array[Char], offset: Int, len: Int): SimpleName =
nameTable.enterIfNew(cs, offset, len)

/** Create a type name from the characters in cs[offset..offset+len-1].
* Assume they are already encoded.
*/
def typeName(cs: Array[Char], offset: Int, len: Int): TypeName =
termName(cs, offset, len).toTypeName
private def termName(cs: Array[Char], offset: Int, len: Int): SimpleName =
termName(CharBuffer.wrap(cs, offset, len))

/** Create a term name from the UTF8 encoded bytes in bs[offset..offset+len-1].
* Assume they are already encoded.
*/
def termName(bs: Array[Byte], offset: Int, len: Int): SimpleName = {
val chars = Codec.fromUTF8(bs, offset, len)
termName(chars, 0, chars.length)
termName(CharBuffer.wrap(chars, 0, chars.length))
}

/** Create a type name from the UTF8 encoded bytes in bs[offset..offset+len-1].
* Assume they are already encoded.
*/
def typeName(bs: Array[Byte], offset: Int, len: Int): TypeName =
termName(bs, offset, len).toTypeName

/** Create a term name from a string.
* See `sliceToTermName` in `Decorators` for a more efficient version
* which however requires a Context for its operation.
/** Create a term name from a sequence of characters.
*/
def termName(s: String): SimpleName = termName(s.toCharArray.nn, 0, s.length)
def termName(s: CharSequence): SimpleName =
nameTable.enterIfNew(s)

/** Create a type name from a string */
def typeName(s: String): TypeName = typeName(s.toCharArray.nn, 0, s.length)
/** Create a type name from a sequence of characters */
def typeName(s: CharSequence): TypeName =
termName(s).toTypeName

/** The type name represented by the empty string */
val EmptyTypeName: TypeName = EmptyTermName.toTypeName
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,8 @@ object SymDenotations {
myTargetName = name

def hasTargetName(name: Name)(using Context): Boolean =
targetName.matchesTargetName(name)
// Don't bother looking at annotations if we're looking for a name that will always match
name.isEmpty || targetName.matchesTargetName(name)

/** The name given in a `@targetName` annotation if one is present, `name` otherwise */
def targetName(using Context): Name =
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3537,7 +3537,7 @@ object TypeComparer {
def show: String =
val lo = if low then " (left is approximated)" else ""
val hi = if high then " (right is approximated)" else ""
lo ++ hi
lo + hi
end ApproxState
type ApproxState = ApproxState.Repr

Expand Down
14 changes: 12 additions & 2 deletions compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,18 @@ object PositionPickler:

/** Pickle the number of lines followed by the size of each line */
def pickleLinesSizes(): Unit = {
val content = source.content()
buf.writeNat(content.count(_ == '\n') + 1) // number of lines
val content = source.textContent()
// Inlined and simplified version of `count` because this is hot,
// and we can't have the optimizer inline the stdlib into the compiler as it may run under a different stdlib.
// Note that we start at 1 since #lines = #separators + 1.
var lineCount = 1
var idx = -1
while
idx = content.indexOf('\n', idx + 1)
idx != -1
do
lineCount += 1
buf.writeNat(lineCount)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one comes from Haoyi's pickling-related perf PR. (I did not make the other changes to this function from that PR, namely inlining indexOf, because now that the content is a string, indexOf is a Java function, not a Scala stdlib extension like it is on arrays.)

var lastIndex = content.indexOf('\n')
buf.writeNat(if lastIndex != -1 then lastIndex else content.length) // size of first line
while lastIndex != -1 do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1853,7 +1853,7 @@ class TreeUnpickler(reader: TastyReader,
*/
def sourceChangeContext(addr: Addr = currentAddr)(using Context): Context = {
val path = sourcePathAt(addr)
if (path.nonEmpty) {
if (!path.isEmpty) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nonEmpty is a Scala extension

val sourceFile = ctx.getSource(path)
posUnpicklerOpt match
case Some(posUnpickler) if !sourceFile.initialized =>
Expand Down
14 changes: 7 additions & 7 deletions compiler/src/dotty/tools/dotc/interactive/Completion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,10 @@ object Completion:
* @param end The end position we'll look for the prefix at
* @return Either the full prefix including the ` or an empty string
*/
private def checkBacktickPrefix(content: Array[Char], start: Int, end: Int): String =
private def checkBacktickPrefix(content: String, start: Int, end: Int): String =
content.lift(start) match
case Some(char) if char == '`' =>
content.slice(start, end).mkString
content.substring(start, end)
case _ =>
""

Expand All @@ -147,24 +147,24 @@ object Completion:
path match
case GenericImportSelector(sel) =>
if sel.isGiven then completionPrefix(sel.bound :: Nil, pos)
else if sel.isWildcard then pos.source.content()(pos.point - 1).toString
else if sel.isWildcard then pos.source.textContent()(pos.point - 1).toString
else completionPrefix(sel.imported :: Nil, pos)

// Foo.`se<TAB> will result in Select(Ident(Foo), <error>)
case (select: untpd.Select) :: _ if select.name == nme.ERROR =>
checkBacktickPrefix(select.source.content(), select.nameSpan.start, select.span.end)
checkBacktickPrefix(select.source.textContent(), select.nameSpan.start, select.span.end)

// import scala.util.chaining.`s<TAB> will result in a Ident(<error>)
case (ident: untpd.Ident) :: _ if ident.name == nme.ERROR =>
checkBacktickPrefix(ident.source.content(), ident.span.start, ident.span.end)
checkBacktickPrefix(ident.source.textContent(), ident.span.start, ident.span.end)

case (tree: untpd.RefTree) :: _ if tree.name != nme.ERROR =>
val nameStart = tree.span.point
val start = if pos.source.content().lift(nameStart).contains('`') then nameStart + 1 else nameStart
val start = if pos.source.textContent().lift(nameStart).contains('`') then nameStart + 1 else nameStart
tree.name.toString.take(pos.span.point - start)

case _ =>
naiveCompletionPrefix(pos.source.content().mkString, pos.point)
naiveCompletionPrefix(pos.source.textContent(), pos.point)
end completionPrefix

private object GenericImportSelector:
Expand Down
6 changes: 1 addition & 5 deletions compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -565,11 +565,7 @@ object JavaScanners {

// Remove the last N characters from the buffer */
def popNChars(n: Int): Unit =
if n > 0 then
val text = litBuf.toString
litBuf.clear()
val trimmed = text.substring(0, text.length - (n min text.length))
trimmed.nn.foreach(litBuf.append)
litBuf.setLength(litBuf.length() - n)

// Drop the line's trailing whitespace
popNChars(trailingWhitespaceLength)
Expand Down
Loading
Loading