Skip to content

Commit 8df17e0

Browse files
committed
Move attachments to a separate hashmap
This saves 4 bytes per tree. Let's see whether that makes a difference, performance-wise.
1 parent e977bfc commit 8df17e0

File tree

9 files changed

+120
-143
lines changed

9 files changed

+120
-143
lines changed

compiler/src/dotty/tools/dotc/Run.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,12 @@ class Run(comp: Compiler, ictx: Context) {
128128
}
129129
}
130130
ctx.informTime(s"$phase ", start)
131+
Stats.record(s"trees created at end of $phase:", ast.Trees.ntrees)
132+
for (unit <- units)
133+
Stats.record("retained typed trees at end of $phase", unit.tpdTree.treeSize)
131134
}
132135
if (!ctx.reporter.hasErrors) Rewrites.writeBack()
133-
for (unit <- units)
134-
Stats.record("retained typed trees at end", unit.tpdTree.treeSize)
135-
Stats.record("total trees at end", ast.Trees.ntrees)
136+
Stats.record("attachments at end", ctx.attachments.size)
136137
}
137138

138139
private sealed trait PrintedTree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,12 @@ object desugar {
8787
}
8888

8989
/** A type definition copied from `tdef` with a rhs typetree derived from it */
90-
def derivedTypeParam(tdef: TypeDef) =
90+
def derivedTypeParam(tdef: TypeDef)(implicit ctx: Context) =
9191
cpy.TypeDef(tdef)(
9292
rhs = new DerivedFromParamTree() withPos tdef.rhs.pos watching tdef)
9393

9494
/** A value definition copied from `vdef` with a tpt typetree derived from it */
95-
def derivedTermParam(vdef: ValDef) =
95+
def derivedTermParam(vdef: ValDef)(implicit ctx: Context) =
9696
cpy.ValDef(vdef)(
9797
tpt = new DerivedFromParamTree() withPos vdef.tpt.pos watching vdef)
9898

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ object Trees {
5656
*/
5757
abstract class Tree[-T >: Untyped] extends Positioned
5858
with Product
59-
with Attachment.Container
59+
with Attachment.Surface
6060
with printing.Showable
6161
with Cloneable {
6262

@@ -311,7 +311,7 @@ object Trees {
311311
private[dotc] def rawMods: untpd.Modifiers =
312312
if (myMods == null) untpd.EmptyModifiers else myMods
313313

314-
def rawComment: Option[Comment] = getAttachment(DocComment)
314+
def rawComment(implicit ctx: Context): Option[Comment] = getAttachment(DocComment)
315315

316316
def withMods(mods: untpd.Modifiers): ThisTree[Untyped] = {
317317
val tree = if (myMods == null || (myMods == mods)) this else clone.asInstanceOf[MemberDef[Untyped]]
@@ -321,7 +321,7 @@ object Trees {
321321

322322
def withFlags(flags: FlagSet): ThisTree[Untyped] = withMods(untpd.Modifiers(flags))
323323

324-
def setComment(comment: Option[Comment]): ThisTree[Untyped] = {
324+
def setComment(comment: Option[Comment])(implicit ctx: Context): ThisTree[Untyped] = {
325325
comment.map(putAttachment(DocComment, _))
326326
asInstanceOf[ThisTree[Untyped]]
327327
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
211211
def watched: Tree = myWatched
212212

213213
/** Install the derived type tree as a dependency on `original` */
214-
def watching(original: DefTree): this.type = {
214+
def watching(original: DefTree)(implicit ctx: Context): this.type = {
215215
myWatched = original
216-
val existing = original.attachmentOrElse(References, Nil)
216+
val existing = original.getAttachment(References).getOrElse(Nil)
217217
original.putAttachment(References, this :: existing)
218218
this
219219
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import config.{Settings, ScalaSettings, Platform, JavaPlatform}
3232
import language.implicitConversions
3333
import DenotTransformers.DenotTransformer
3434
import util.Property.Key
35+
import util.Attachment
3536
import xsbti.AnalysisCallback
3637

3738
object Contexts {
@@ -652,6 +653,11 @@ object Contexts {
652653

653654
private[core] var denotTransformers: Array[DenotTransformer] = _
654655

656+
// Trees state
657+
658+
private[dotc] val attachments =
659+
new java.util.IdentityHashMap[Attachment.Surface, Map[Key[_], Any]]
660+
655661
// Printers state
656662
/** Number of recursive invocations of a show method on current stack */
657663
private[dotc] var toTextRecursions = 0
@@ -664,6 +670,7 @@ object Contexts {
664670
def reset() = {
665671
for ((_, set) <- uniqueSets) set.clear()
666672
errorTypeMsg.clear()
673+
attachments.clear()
667674
}
668675

669676
// Test that access is single threaded

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ trait NamerContextOps { this: Context =>
6868
def go(ctx: Context): Symbol = {
6969
ctx.typeAssigner match {
7070
case typer: Typer =>
71-
tree.getAttachment(typer.SymOfTree) match {
71+
tree.getAttachment(typer.SymOfTree)(ctx) match {
7272
case Some(sym) => sym
7373
case None =>
7474
var cx = ctx.outer
@@ -226,7 +226,7 @@ class Namer { typer: Typer =>
226226

227227
/** Record `sym` as the symbol defined by `tree` */
228228
def recordSym(sym: Symbol, tree: Tree)(implicit ctx: Context): Symbol = {
229-
val refs = tree.attachmentOrElse(References, Nil)
229+
val refs = tree.getAttachment(References).getOrElse(Nil)
230230
if (refs.nonEmpty) {
231231
tree.removeAttachment(References)
232232
refs foreach (_.pushAttachment(OriginalSymbol, sym))
@@ -373,7 +373,7 @@ class Namer { typer: Typer =>
373373

374374
/** The expanded version of this tree, or tree itself if not expanded */
375375
def expanded(tree: Tree)(implicit ctx: Context): Tree = tree match {
376-
case ddef: DefTree => ddef.attachmentOrElse(ExpandedTree, ddef)
376+
case ddef: DefTree => ddef.getAttachment(ExpandedTree).getOrElse(ddef)
377377
case _ => tree
378378
}
379379

Lines changed: 45 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,73 @@
1-
package dotty.tools.dotc.util
1+
package dotty.tools.dotc
2+
package util
3+
4+
import Property.Key
5+
import core.Contexts.Context
6+
7+
/** Typed Key/Value pairs */
8+
case class Attachment[V](key: Key[V], value: V) {
9+
Stats.record("attachment")
10+
}
211

3-
/** A class inheriting from Attachment.Container supports
4-
* adding, removing and lookup of attachments. Attachments are typed key/value pairs.
5-
*/
612
object Attachment {
7-
import Property.Key
813

9-
/** An implementation trait for attachments.
10-
* Clients should inherit from Container instead.
11-
*/
12-
trait LinkSource {
13-
private[Attachment] var next: Link[_]
14+
/** A surface to which attachments can be attached */
15+
trait Surface {
1416

1517
/** Optionally get attachment corresponding to `key` */
16-
final def getAttachment[V](key: Key[V]): Option[V] = {
17-
val nx = next
18-
if (nx == null) None
19-
else if (nx.key eq key) Some(nx.value.asInstanceOf[V])
20-
else nx.getAttachment[V](key)
18+
final def getAttachment[V](key: Key[V])(implicit ctx: Context): Option[V] = {
19+
val as = ctx.attachments.get(this)
20+
if (as == null) None
21+
else as.get(key).asInstanceOf[Option[V]]
2122
}
2223

2324
/** The attachment corresponding to `key`.
2425
* @throws NoSuchElementException if no attachment with key exists
2526
*/
26-
final def attachment[V](key: Key[V]): V = {
27-
val nx = next
28-
if (nx == null) throw new NoSuchElementException
29-
else if (nx.key eq key) nx.value.asInstanceOf[V]
30-
else nx.attachment(key)
31-
}
32-
33-
/** The attachment corresponding to `key`, or `default`
34-
* if no attachment with `key` exists.
35-
*/
36-
final def attachmentOrElse[V](key: Key[V], default: V): V = {
37-
val nx = next
38-
if (nx == null) default
39-
else if (nx.key eq key) nx.value.asInstanceOf[V]
40-
else nx.attachmentOrElse(key, default)
41-
}
27+
final def attachment[V](key: Key[V])(implicit ctx: Context): V =
28+
getAttachment(key).get
4229

4330
/** Add attachment with given `key` and `value`.
4431
* @return Optionally, the old attachment with given `key` if one existed before.
4532
* The new attachment is added at the position of the old one, or at the end
4633
* if no attachment with same `key` existed.
4734
*/
48-
final def putAttachment[V](key: Key[V], value: V): Option[V] = {
49-
val nx = next
50-
if (nx == null) {
51-
next = new Link(key, value, null)
52-
None
53-
}
54-
else if (nx.key eq key) {
55-
next = new Link(key, value, nx.next)
56-
Some(nx.value.asInstanceOf[V])
57-
}
58-
else nx.putAttachment(key, value)
35+
final def putAttachment[V](key: Key[V], value: V)(implicit ctx: Context): Option[V] = {
36+
val as = ctx.attachments.get(this)
37+
val m: Map[Key[_], Any] = if (as == null) Map.empty else as // Do we need the type annotation?
38+
ctx.attachments.put(this, m.updated(key, value))
39+
m.get(key).asInstanceOf[Option[V]]
40+
}
41+
42+
final def pushAttachment[V](key: Key[V], value: V)(implicit ctx: Context): Unit = {
43+
assert(!getAttachment(key).isDefined, s"duplicate attachment for key $key")
44+
putAttachment(key, value)
5945
}
6046

6147
/** Remove attachment with given `key`, if it exists.
6248
* @return Optionally, the removed attachment with given `key` if one existed before.
6349
*/
64-
final def removeAttachment[V](key: Key[V]): Option[V] = {
65-
val nx = next
66-
if (nx == null)
67-
None
68-
else if (nx.key eq key) {
69-
next = nx.next
70-
Some(nx.value.asInstanceOf[V])
50+
final def removeAttachment[V](key: Key[V])(implicit ctx: Context): Option[V] = {
51+
val as = ctx.attachments.get(this)
52+
if (as == null) None
53+
else as.get(key) match {
54+
case None => None
55+
case some =>
56+
val m = as - key
57+
if (m.isEmpty) ctx.attachments.remove(this)
58+
else ctx.attachments.put(this, m)
59+
some.asInstanceOf[Option[V]]
7160
}
72-
else nx.removeAttachment(key)
7361
}
7462

75-
/** The list of all keys and values attached to this container. */
76-
final def allAttachments: List[(Key[_], Any)] = {
77-
val nx = next
78-
if (nx == null) Nil else (nx.key, nx.value) :: nx.allAttachments
79-
}
80-
}
81-
82-
/** A private, concrete implementation class linking attachments.
83-
*/
84-
private[Attachment] class Link[+V](val key: Key[V], val value: V, var next: Link[_])
85-
extends LinkSource
86-
87-
/** A trait for objects that can contain attachments */
88-
trait Container extends LinkSource {
89-
private[Attachment] var next: Link[_] = null
90-
91-
final def pushAttachment[V](key: Key[V], value: V): Unit = {
92-
assert(!getAttachment(key).isDefined, s"duplicate attachment for key $key")
93-
next = new Link(key, value, next)
63+
/** The list of all keys and values attached to this surface. */
64+
final def allAttachments(implicit ctx: Context): List[(Key[_], Any)] = {
65+
val as = ctx.attachments.get(this)
66+
if (as == null) Nil
67+
else as.toList
9468
}
9569

96-
final def removeAllAttachments() =
97-
next = null
70+
final def removeAllAttachments()(implicit ctx: Context) =
71+
ctx.attachments.remove(this)
9872
}
9973
}

compiler/test/dotty/tools/dotc/parsing/DocstringTest.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,9 @@ trait DocstringTest extends DottyTest {
2525
assert(false, s"Couldn't match resulting AST to expected AST in: $x")
2626
}
2727

28-
def checkFrontend(source: String)(docAssert: PartialFunction[Tree[Untyped], Unit]): Unit = {
28+
def checkFrontend(source: String)(docAssert: Context => PartialFunction[Tree[Untyped], Unit]): Unit = {
2929
checkCompile("frontend", source) { (_, ctx) =>
30-
implicit val c: Context = ctx
31-
(docAssert orElse defaultAssertion)(ctx.compilationUnit.untpdTree)
30+
(docAssert(ctx) orElse defaultAssertion)(ctx.compilationUnit.untpdTree)
3231
}
3332
}
3433
}

0 commit comments

Comments
 (0)