Skip to content

Commit 4594457

Browse files
authored
Merge pull request #9690 from dotty-staging/reduce-allocs
Avoid allocations in more places
2 parents 9294593 + 71737f1 commit 4594457

14 files changed

+84
-63
lines changed

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ object Trees {
342342
val point = span.point
343343
if (rawMods.is(Synthetic) || name.toTermName == nme.ERROR) Span(point)
344344
else {
345-
val realName = name.stripModuleClassSuffix.lastPart.toString
345+
val realName = name.stripModuleClassSuffix.lastPart
346346
Span(point, point + realName.length, point)
347347
}
348348
}

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,8 +1172,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
11721172

11731173
// convert a numeric with a toXXX method
11741174
def primitiveConversion(tree: Tree, numericCls: Symbol)(using Context): Tree = {
1175-
val mname = ("to" + numericCls.name).toTermName
1176-
val conversion = tree.tpe member mname
1175+
val mname = "to".concat(numericCls.name)
1176+
val conversion = tree.tpe member(mname)
11771177
if (conversion.symbol.exists)
11781178
tree.select(conversion.symbol.termRef).ensureApplied
11791179
else if (tree.tpe.widen isRef numericCls)

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,12 @@ object Contexts {
918918
private[Contexts] val comparers = new mutable.ArrayBuffer[TypeComparer]
919919
private[Contexts] var comparersInUse: Int = 0
920920

921-
private[dotc] var nameCharBuffer = new Array[Char](256)
921+
private var charArray = new Array[Char](256)
922+
923+
def sharedCharArray(len: Int): Array[Char] =
924+
while len > charArray.length do
925+
charArray = new Array[Char](charArray.length * 2)
926+
charArray
922927

923928
def reset(): Unit = {
924929
for ((_, set) <- uniqueSets) set.clear()

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,25 @@ object Decorators {
3939
* This avoids some allocation relative to `termName(s)`
4040
*/
4141
def sliceToTermName(start: Int, end: Int)(using Context): SimpleName =
42-
val base = ctx.base
4342
val len = end - start
44-
while len > base.nameCharBuffer.length do
45-
base.nameCharBuffer = new Array[Char](base.nameCharBuffer.length * 2)
46-
s.getChars(start, end, base.nameCharBuffer, 0)
47-
termName(base.nameCharBuffer, 0, len)
43+
val chars = ctx.base.sharedCharArray(len)
44+
s.getChars(start, end, chars, 0)
45+
termName(chars, 0, len)
4846

4947
def sliceToTypeName(start: Int, end: Int)(using Context): TypeName =
5048
sliceToTermName(start, end).toTypeName
5149

50+
def concat(name: Name)(using Context): SimpleName = name match
51+
case name: SimpleName =>
52+
val len = s.length + name.length
53+
var chars = ctx.base.sharedCharArray(len)
54+
s.getChars(0, s.length, chars, 0)
55+
if name.length != 0 then name.getChars(0, name.length, chars, s.length)
56+
termName(chars, 0, len)
57+
case name: TypeName => s.concat(name.toTermName)
58+
case _ => termName(s.concat(name.toString))
59+
end extension
60+
5261
/** Implements a findSymbol method on iterators of Symbols that
5362
* works like find but avoids Option, replacing None with NoSymbol.
5463
*/

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import NameOps._
77
import StdNames._
88
import NameTags._
99
import Contexts._
10+
import Decorators._
1011
import collection.mutable
1112

1213
import scala.annotation.internal.sharable
@@ -208,6 +209,8 @@ object NameKinds {
208209
extends NumberedNameKind(UNIQUE, s"Unique $separator") {
209210
override def definesNewName: Boolean = true
210211

212+
val separatorName = separator.toTermName
213+
211214
def mkString(underlying: TermName, info: ThisInfo): String = {
212215
val safePrefix = str.sanitize(underlying.toString) + separator
213216
safePrefix + info.num
@@ -226,10 +229,10 @@ object NameKinds {
226229

227230
/** An extractor for unique names of arbitrary kind */
228231
object AnyUniqueName {
229-
def unapply(name: DerivedName): Option[(TermName, String, Int)] = name match {
232+
def unapply(name: DerivedName): Option[(TermName, TermName, Int)] = name match {
230233
case DerivedName(qual, info: NumberedInfo) =>
231234
info.kind match {
232-
case unique: UniqueNameKind => Some((qual, unique.separator, info.num))
235+
case unique: UniqueNameKind => Some((qual, unique.separatorName, info.num))
233236
case _ => None
234237
}
235238
case _ => None

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import scala.internal.Chars
88
import Chars.isOperatorPart
99
import Definitions._
1010
import nme._
11+
import Decorators.concat
1112

1213
object NameOps {
1314

@@ -138,7 +139,7 @@ object NameOps {
138139
case _ => false
139140

140141
/** Add an `extension_` in front of this name */
141-
def toExtensionName = termName("extension_" ++ name.toString)
142+
def toExtensionName(using Context): SimpleName = "extension_".concat(name)
142143

143144
/** Drop `extension_` in front of this name, if it has this prefix */
144145
def dropExtension = name match

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,13 @@ object Names {
337337
def head: Char = apply(0)
338338
def last: Char = apply(length - 1)
339339

340+
/** Copy character slice (from until end) to character array starting at `dstStart`.
341+
* @pre Destination must have enough space to hold all characters of this name.
342+
*/
343+
def getChars(from: Int, end: Int, dst: Array[Char], dstStart: Int): Unit =
344+
assert(0 <= from && from <= end && end <= length)
345+
Array.copy(chrs, start + from, dst, dstStart, end - from)
346+
340347
override def asSimpleName: SimpleName = this
341348
override def toSimpleName: SimpleName = this
342349
override final def mangle: SimpleName = encode

compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class NameBuffer extends TastyBuffer(10000) {
3535
case AnyQualifiedName(prefix, name) =>
3636
nameIndex(prefix); nameIndex(name)
3737
case AnyUniqueName(original, separator, num) =>
38-
nameIndex(separator.toTermName)
38+
nameIndex(separator)
3939
if (!original.isEmpty) nameIndex(original)
4040
case DerivedName(original, _) =>
4141
nameIndex(original)
@@ -82,7 +82,7 @@ class NameBuffer extends TastyBuffer(10000) {
8282
withLength { writeNameRef(prefix); writeNameRef(name) }
8383
case AnyUniqueName(original, separator, num) =>
8484
withLength {
85-
writeNameRef(separator.toTermName)
85+
writeNameRef(separator)
8686
writeNat(num)
8787
if (!original.isEmpty) writeNameRef(original)
8888
}

compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ class PositionPickler(
4646
lastIndex = index
4747
lastSpan = span
4848

49-
pickledIndices += index
49+
pickledIndices.addOne(index)
50+
// Note `+=` boxes since it is a generic @inline function in `SetOps`
51+
// that forwards to the specialized `addOne` in `BitSet`. Since the
52+
// current backend does not implement `@inline` we are missing the
53+
// specialization.
5054
}
5155

5256
def pickleSource(source: SourceFile): Unit = {

compiler/src/dotty/tools/dotc/parsing/Scanners.scala

Lines changed: 9 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import core.StdNames._, core.Comments._
77
import util.SourceFile
88
import java.lang.Character.isDigit
99
import scala.internal.Chars._
10-
import util.SourcePosition
10+
import util.{SourcePosition, CharBuffer}
1111
import util.Spans.Span
1212
import config.Config
1313
import config.Printers.lexical
@@ -21,38 +21,6 @@ import config.Feature.migrateTo3
2121
import config.SourceVersion._
2222
import reporting.Message
2323

24-
object Cbufs {
25-
import java.lang.StringBuilder
26-
27-
private final val TargetCapacity = 256
28-
29-
opaque type Cbuf = StringBuilder
30-
object Cbuf:
31-
def apply(): Cbuf = new StringBuilder(TargetCapacity)
32-
33-
extension (buf: Cbuf):
34-
def clear(): Unit = {
35-
if buf.capacity() > TargetCapacity then
36-
buf.setLength(TargetCapacity)
37-
buf.trimToSize()
38-
end if
39-
buf.setLength(0)
40-
}
41-
def toCharArray: Array[Char] = {
42-
val n = buf.length()
43-
val res = new Array[Char](n)
44-
buf.getChars(0, n, res, 0)
45-
res
46-
}
47-
def append(c: Char): buf.type = { buf.append(c) ; buf }
48-
def isEmpty: Boolean = buf.length() == 0
49-
def length: Int = buf.length()
50-
def last: Char = buf.charAt(buf.length() - 1)
51-
end extension
52-
}
53-
54-
import Cbufs._
55-
5624
object Scanners {
5725

5826
/** Offset into source character array */
@@ -142,22 +110,16 @@ object Scanners {
142110

143111
/** A character buffer for literals
144112
*/
145-
protected val litBuf = Cbuf()
113+
protected val litBuf = CharBuffer()
146114

147115
/** append Unicode character to "litBuf" buffer
148116
*/
149117
protected def putChar(c: Char): Unit = litBuf.append(c)
150118

151-
/** Return buffer contents and clear */
152-
def flushBuf(buf: Cbuf): String = {
153-
val str = buf.toString
154-
buf.clear()
155-
str
156-
}
157-
158119
/** Clear buffer and set name and token */
159120
def finishNamed(idtoken: Token = IDENTIFIER, target: TokenData = this): Unit = {
160-
target.name = termName(flushBuf(litBuf))
121+
target.name = termName(litBuf.chars, 0, litBuf.length)
122+
litBuf.clear()
161123
target.token = idtoken
162124
if (idtoken == IDENTIFIER)
163125
target.token = toToken(target.name)
@@ -168,7 +130,8 @@ object Scanners {
168130

169131
/** Clear buffer and set string */
170132
def setStrVal(): Unit =
171-
strVal = flushBuf(litBuf)
133+
strVal = litBuf.toString
134+
litBuf.clear()
172135

173136
@inline def isNumberSeparator(c: Char): Boolean = c == '_'
174137

@@ -241,7 +204,7 @@ object Scanners {
241204
def getDocComment(pos: Int): Option[Comment] = docstringMap.get(pos)
242205

243206
/** A buffer for comments */
244-
private val commentBuf = Cbuf()
207+
private val commentBuf = CharBuffer()
245208

246209
private def handleMigration(keyword: Token): Token =
247210
if keyword == ERASED && !ctx.settings.YerasedTerms.value then IDENTIFIER
@@ -888,7 +851,8 @@ object Scanners {
888851
def finishComment(): Boolean = {
889852
if (keepComments) {
890853
val pos = Span(start, charOffset - 1, start)
891-
val comment = Comment(pos, flushBuf(commentBuf))
854+
val comment = Comment(pos, commentBuf.toString)
855+
commentBuf.clear()
892856
commentPosBuf += pos
893857

894858
if (comment.isDocComment)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class ElimOpaque extends MiniPhase with DenotTransformer {
6666
if sym == defn.Any_== || sym == defn.Any_!= then
6767
tree match
6868
case Apply(Select(receiver, name: TermName), args)
69-
if atPhase(thisPhase)(receiver.tpe.widen.typeSymbol.isOpaqueAlias) =>
69+
if atPhase(thisPhase)(receiver.tpe.widen.dealias.typeSymbol.isOpaqueAlias) =>
7070
applyOverloaded(receiver, name, args, Nil, defn.BooleanType)
7171
case _ =>
7272
tree

compiler/src/dotty/tools/dotc/typer/Deriving.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ trait Deriving {
4747
* @param reportErrors Report an error if an instance with the same name exists already
4848
*/
4949
private def addDerivedInstance(clsName: Name, info: Type, pos: SrcPos): Unit = {
50-
val instanceName = s"derived$$$clsName".toTermName
50+
val instanceName = "derived$".concat(clsName)
5151
if (ctx.denotNamed(instanceName).exists)
5252
report.error(i"duplicate type class derivation for $clsName", pos)
5353
else

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ object ProtoTypes {
137137
extends CachedProxyType with ProtoType with ValueTypeOrProto {
138138

139139
private var myExtensionName: TermName = null
140-
def extensionName: TermName =
140+
def extensionName(using Context): TermName =
141141
if myExtensionName == null then myExtensionName = name.toExtensionName
142142
myExtensionName
143143

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package dotty.tools
2+
package dotc
3+
package util
4+
5+
/** A character buffer that exposes the internal array for reading.
6+
* That way we can avoid copying when converting to names.
7+
*/
8+
class CharBuffer(initialSize: Int = 1024):
9+
private var cs: Array[Char] = new Array[Char](initialSize)
10+
private var len: Int = 0
11+
12+
def append(ch: Char): Unit =
13+
if len == cs.length then
14+
val cs1 = new Array[Char](len * 2)
15+
Array.copy(cs, 0, cs1, 0, len)
16+
cs = cs1
17+
cs(len) = ch
18+
len += 1
19+
20+
def chars = cs
21+
def length = len
22+
def isEmpty: Boolean = len == 0
23+
def last: Char = cs(len - 1)
24+
def clear(): Unit = len = 0
25+
26+
override def toString = String(cs, 0, len)
27+
28+

0 commit comments

Comments
 (0)