Skip to content

Commit 96c18fb

Browse files
committed
Trial: Use a sparse array representation for Tree-address maps.
Make use of the fact that every tree has a unique id, so we can use that as an index into a sparse array.
1 parent 13c3210 commit 96c18fb

File tree

2 files changed

+246
-5
lines changed

2 files changed

+246
-5
lines changed

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import dotty.tools.tasty.TastyBuffer
88
import TastyBuffer.{Addr, NoAddr, AddrWidth}
99

1010
import util.Util.bestFit
11+
import util.SparseIntArray
1112
import config.Printers.pickling
1213
import ast.untpd.Tree
1314

@@ -22,16 +23,27 @@ class TreeBuffer extends TastyBuffer(50000) {
2223

2324
/** A map from trees to the address at which a tree is pickled. */
2425
private val treeAddrs = new java.util.IdentityHashMap[Tree, Any] // really: Addr | Null
26+
private val treeAddrs2 = SparseIntArray()
2527

2628
def registerTreeAddr(tree: Tree): Addr = treeAddrs.get(tree) match {
27-
case null => treeAddrs.put(tree, currentAddr); currentAddr
29+
case null =>
30+
treeAddrs.put(tree, currentAddr)
31+
treeAddrs2(tree.uniqueId) = currentAddr.index
32+
currentAddr
2833
case addr: Addr => addr
2934
}
3035

31-
def addrOfTree(tree: Tree): Addr = treeAddrs.get(tree) match {
32-
case null => NoAddr
33-
case addr: Addr => addr
34-
}
36+
def addrOfTree(tree: Tree): Addr =
37+
val old = treeAddrs.get(tree) match {
38+
case null => NoAddr
39+
case addr: Addr => addr
40+
}
41+
val now =
42+
val idx = tree.uniqueId
43+
if treeAddrs2.contains(idx) then Addr(treeAddrs2(idx))
44+
else NoAddr
45+
assert(old == now)
46+
now
3547

3648
private def offset(i: Int): Addr = Addr(offsets(i))
3749

@@ -164,6 +176,7 @@ class TreeBuffer extends TastyBuffer(50000) {
164176
case addr: Addr => treeAddrs.put(tree, adjusted(addr))
165177
}
166178
}
179+
treeAddrs2.transform((id, addr) => adjusted(Addr(addr)).index)
167180
}
168181

169182
/** Final assembly, involving the following steps:
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
package dotty.tools.dotc
2+
package util
3+
4+
import java.util.NoSuchElementException
5+
6+
class SparseIntArray:
7+
import SparseIntArray._
8+
9+
private var siz: Int = 0
10+
private var root: Node = LeafNode()
11+
12+
private def grow() =
13+
val newRoot = InnerNode(root.level + 1)
14+
newRoot.elems(0) = root
15+
root = newRoot
16+
17+
private def capacity: Int = root.elemSize * NodeSize
18+
19+
def size = siz
20+
21+
def contains(index: Int): Boolean =
22+
0 <= index && index < capacity && root.contains(index)
23+
24+
def apply(index: Int): Value =
25+
require(index >= 0)
26+
if index >= capacity then throw NoSuchElementException()
27+
root.apply(index)
28+
29+
def update(index: Int, value: Value): Unit =
30+
require(index >= 0)
31+
while capacity <= index do grow()
32+
if !root.update(index, value) then siz += 1
33+
34+
/** Remove element at `index` if it is present
35+
* @return element was present
36+
*/
37+
def remove(index: Int): Boolean =
38+
require(index >= 0)
39+
index < capacity && {
40+
val result = root.remove(index)
41+
if result then siz -= 1
42+
result
43+
}
44+
45+
def foreachBinding(op: (Int, Value) => Unit): Unit =
46+
root.foreachBinding(op, 0)
47+
48+
def transform(op: (Int, Value) => Value): Unit =
49+
root.transform(op, 0)
50+
51+
/** Access to some info about low-level representation */
52+
def repr: Repr = root
53+
54+
override def toString =
55+
val b = StringBuilder() ++= "SparseIntArray("
56+
var first = true
57+
foreachBinding { (idx, elem) =>
58+
if first then first = false else b ++= ", "
59+
b ++= s"$idx -> $elem"
60+
}
61+
b ++= ")"
62+
b.toString
63+
64+
object SparseIntArray:
65+
type Value = Int
66+
67+
private inline val NodeSizeLog = 5
68+
private inline val NodeSize = 1 << NodeSizeLog
69+
70+
/** The exposed representation. Should be used just for nodeCount and
71+
* low-level toString.
72+
*/
73+
abstract class Repr:
74+
def nodeCount: Int
75+
76+
private abstract class Node(val level: Int) extends Repr:
77+
private[SparseIntArray] def elemShift = level * NodeSizeLog
78+
private[SparseIntArray] def elemSize = 1 << elemShift
79+
private[SparseIntArray] def elemMask = elemSize - 1
80+
def contains(index: Int): Boolean
81+
def apply(index: Int): Value
82+
def update(index: Int, value: Value): Boolean
83+
def remove(index: Int): Boolean
84+
def isEmpty: Boolean
85+
def foreachBinding(op: (Int, Value) => Unit, offset: Int): Unit
86+
def transform(op: (Int, Value) => Value, offset: Int): Unit
87+
def nodeCount: Int
88+
end Node
89+
90+
private class LeafNode extends Node(0):
91+
private val elems = new Array[Value](NodeSize)
92+
private var present: Int = 0
93+
94+
def contains(index: Int): Boolean =
95+
(present & (1 << index)) != 0
96+
97+
def apply(index: Int) =
98+
if !contains(index) then throw NoSuchElementException()
99+
elems(index)
100+
101+
def update(index: Int, value: Value): Boolean =
102+
elems(index) = value
103+
val result = contains(index)
104+
present = present | (1 << index)
105+
result
106+
107+
def remove(index: Int): Boolean =
108+
val result = contains(index)
109+
present = present & ~(1 << index)
110+
result
111+
112+
def isEmpty = present == 0
113+
114+
def foreachBinding(op: (Int, Value) => Unit, offset: Int): Unit =
115+
var i = 0
116+
while i < NodeSize do
117+
if contains(i) then op(offset + i, elems(i))
118+
i += 1
119+
120+
def transform(op: (Int, Value) => Value, offset: Int): Unit =
121+
var i = 0
122+
while i < NodeSize do
123+
if contains(i) then elems(i) = op(offset + i, elems(i))
124+
i += 1
125+
126+
def nodeCount = 1
127+
128+
override def toString =
129+
elems
130+
.zipWithIndex
131+
.filter((elem, idx) => contains(idx))
132+
.map((elem, idx) => s"$idx -> $elem").mkString(s"0#(", ", ", ")")
133+
end LeafNode
134+
135+
private class InnerNode(level: Int) extends Node(level):
136+
private[SparseIntArray] val elems = new Array[Node](NodeSize)
137+
private var empty: Boolean = true
138+
139+
def contains(index: Int): Boolean =
140+
val elem = elems(index >>> elemShift)
141+
elem != null && elem.contains(index & elemMask)
142+
143+
def apply(index: Int): Value =
144+
val elem = elems(index >>> elemShift)
145+
if elem == null then throw NoSuchElementException()
146+
elem.apply(index & elemMask)
147+
148+
def update(index: Int, value: Value): Boolean =
149+
empty = false
150+
var elem = elems(index >>> elemShift)
151+
if elem == null then
152+
elem = newNode(level - 1)
153+
elems(index >>> elemShift) = elem
154+
elem.update(index & elemMask, value)
155+
156+
def remove(index: Int): Boolean =
157+
val elem = elems(index >>> elemShift)
158+
if elem == null then false
159+
else
160+
val result = elem.remove(index & elemMask)
161+
if elem.isEmpty then
162+
elems(index >>> elemShift) = null
163+
var i = 0
164+
while i < NodeSize && elems(i) == null do i += 1
165+
if i == NodeSize then empty = true
166+
result
167+
168+
def isEmpty = empty
169+
170+
def foreachBinding(op: (Int, Value) => Unit, offset: Int): Unit =
171+
var i = 0
172+
while i < NodeSize do
173+
if elems(i) != null then
174+
elems(i).foreachBinding(op, offset + i * elemSize)
175+
i += 1
176+
177+
def transform(op: (Int, Value) => Value, offset: Int): Unit =
178+
var i = 0
179+
while i < NodeSize do
180+
if elems(i) != null then
181+
elems(i).transform(op, offset + i * elemSize)
182+
i += 1
183+
184+
def nodeCount =
185+
1 + elems.filter(_ != null).map(_.nodeCount).sum
186+
187+
override def toString =
188+
elems
189+
.zipWithIndex
190+
.filter((elem, idx) => elem != null)
191+
.map((elem, idx) => s"$idx -> $elem").mkString(s"$level#(", ", ", ")")
192+
end InnerNode
193+
194+
private def newNode(level: Int): Node =
195+
if level == 0 then LeafNode() else InnerNode(level)
196+
197+
end SparseIntArray
198+
199+
@main def Test =
200+
val a = SparseIntArray()
201+
println(s"a = $a")
202+
a(1) = 22
203+
println(s"a = $a")
204+
a(222) = 33
205+
println(s"a = $a")
206+
a(55555) = 44
207+
println(s"a = $a")
208+
assert(a.size == 3, a)
209+
assert(a.contains(1), a)
210+
assert(a.contains(222), a)
211+
assert(a.contains(55555), a)
212+
assert(!a.contains(2))
213+
assert(!a.contains(20000000))
214+
a(222) = 44
215+
assert(a.size == 3)
216+
assert(a(1) == 22)
217+
assert(a(222) == 44)
218+
assert(a(55555) == 44)
219+
assert(a.remove(1))
220+
println(s"a = $a")
221+
assert(a(222) == 44, a)
222+
assert(a.remove(55555))
223+
assert(a(222) == 44, a)
224+
assert(a.size == 1)
225+
assert(!a.contains(1))
226+
assert(!a.remove(55555))
227+
assert(a.remove(222))
228+
assert(a.size == 0)

0 commit comments

Comments
 (0)