Skip to content

Commit e2ade80

Browse files
authored
Merge pull request #9900 from dotty-staging/fix-sourcepos
Make source a field in Positioned nodes
2 parents 371d8f5 + 0bb3107 commit e2ade80

File tree

11 files changed

+332
-349
lines changed

11 files changed

+332
-349
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class Driver {
7070
val summary = CompilerCommand.distill(args)(using ictx)
7171
ictx.setSettings(summary.sstate)
7272
MacroClassLoader.init(ictx)
73-
Positioned.updateDebugPos(using ictx)
73+
Positioned.init(using ictx)
7474

7575
inContext(ictx) {
7676
if !ctx.settings.YdropComments.value || ctx.mode.is(Mode.ReadComments) then

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

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,36 @@ import java.io.{ PrintWriter }
1919
/** A base class for things that have positions (currently: modifiers and trees)
2020
*/
2121
abstract class Positioned(implicit @constructorOnly src: SourceFile) extends SrcPos, Product, Cloneable {
22+
import Positioned.{ids, nextId, debugId}
2223

23-
private var myUniqueId: Int = _
2424
private var mySpan: Span = _
2525

26-
/** A unique identifier. Among other things, used for determining the source file
27-
* component of the position.
26+
/** A unique identifier in case -Yshow-tree-ids, or -Ydebug-tree-with-id
27+
* is set, -1 otherwise.
2828
*/
29-
def uniqueId: Int = myUniqueId
29+
def uniqueId: Int =
30+
if ids != null && ids.containsKey(this) then ids.get(this) else -1
3031

31-
def uniqueId_=(id: Int): Unit = {
32-
def printTrace() = {
33-
println(s"Debug tree (id=${Positioned.debugId}) creation \n$this\n")
34-
Reporter.displayPrompt(Console.in, new PrintWriter(Console.err, true))
35-
}
36-
if (Positioned.debugId == id) printTrace()
37-
myUniqueId = id
38-
}
32+
private def allocateId() =
33+
if ids != null then
34+
val ownId = nextId
35+
nextId += 1
36+
ids.put(this, ownId)
37+
if ownId == debugId then
38+
println(s"Debug tree (id=$debugId) creation \n$this\n")
39+
Reporter.displayPrompt(Console.in, new PrintWriter(Console.err, true))
40+
41+
allocateId()
3942

4043
/** The span part of the item's position */
4144
def span: Span = mySpan
4245

4346
def span_=(span: Span): Unit =
4447
mySpan = span
4548

46-
uniqueId = src.nextId
4749
span = envelope(src)
4850

49-
def source: SourceFile = SourceFile.fromId(uniqueId)
51+
val source: SourceFile = src
5052
def sourcePos(using Context): SourcePosition = source.atSpan(span)
5153

5254
/** This positioned item, widened to `SrcPos`. Used to make clear we only need the
@@ -125,7 +127,7 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src
125127
/** Clone this node but assign it a fresh id which marks it as a node in `file`. */
126128
def cloneIn(src: SourceFile): this.type = {
127129
val newpd: this.type = clone.asInstanceOf[this.type]
128-
newpd.uniqueId = src.nextId
130+
newpd.allocateId()
129131
// assert(newpd.uniqueId != 2208, s"source = $this, ${this.uniqueId}, ${this.span}")
130132
newpd
131133
}
@@ -237,8 +239,14 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src
237239
}
238240

239241
object Positioned {
240-
@sharable private[Positioned] var debugId = Int.MinValue
242+
@sharable private var debugId = Int.MinValue
243+
@sharable private var ids: java.util.WeakHashMap[Positioned, Int] = null
244+
@sharable private var nextId: Int = 0
241245

242-
def updateDebugPos(using Context): Unit =
246+
def init(using Context): Unit =
243247
debugId = ctx.settings.YdebugTreeWithId.value
248+
if ids == null && ctx.settings.YshowTreeIds.value
249+
|| debugId != ctx.settings.YdebugTreeWithId.default
250+
then
251+
ids = java.util.WeakHashMap()
244252
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ object Trees {
221221
}
222222
}
223223

224-
override def hashCode(): Int = uniqueId // for debugging; was: System.identityHashCode(this)
224+
override def hashCode(): Int = System.identityHashCode(this)
225225
override def equals(that: Any): Boolean = this eq that.asInstanceOf[AnyRef]
226226
}
227227

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

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

1010
import util.Util.bestFit
11-
import util.SparseIntArray
1211
import config.Printers.pickling
1312
import ast.untpd.Tree
1413

@@ -21,23 +20,20 @@ class TreeBuffer extends TastyBuffer(50000) {
2120
private var delta: Array[Int] = _
2221
private var numOffsets = 0
2322

24-
/** A map from tree unique ids to the address index at which a tree is pickled.
25-
* Note that trees are looked up by reference equality,
26-
* so one can reliably use this function only directly after `pickler`.
27-
*/
28-
private val addrOfTree = SparseIntArray()
23+
/** A map from trees to the address at which a tree is pickled. */
24+
private val treeAddrs = util.IntMap[Tree](initialCapacity = 8192)
2925

3026
def registerTreeAddr(tree: Tree): Addr =
31-
val id = tree.uniqueId
32-
if addrOfTree.contains(id) then Addr(addrOfTree(id))
33-
else
34-
addrOfTree(tree.uniqueId) = currentAddr.index
27+
val idx = treeAddrs(tree)
28+
if idx < 0 then
29+
treeAddrs(tree) = currentAddr.index
3530
currentAddr
31+
else
32+
Addr(idx)
3633

3734
def addrOfTree(tree: Tree): Addr =
38-
val idx = tree.uniqueId
39-
if addrOfTree.contains(idx) then Addr(addrOfTree(idx))
40-
else NoAddr
35+
val idx = treeAddrs(tree)
36+
if idx < 0 then NoAddr else Addr(idx)
4137

4238
private def offset(i: Int): Addr = Addr(offsets(i))
4339

@@ -163,7 +159,10 @@ class TreeBuffer extends TastyBuffer(50000) {
163159
}
164160

165161
def adjustTreeAddrs(): Unit =
166-
addrOfTree.transform((id, addr) => adjusted(Addr(addr)).index)
162+
var i = 0
163+
while i < treeAddrs.size do
164+
treeAddrs.setValue(i, adjusted(Addr(treeAddrs.value(i))).index)
165+
i += 1
167166

168167
/** Final assembly, involving the following steps:
169168
* - compute deltas

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

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,6 @@ object Inliner {
7070
* and body that replace it.
7171
*/
7272
def inlineCall(tree: Tree)(using Context): Tree = {
73-
val startId = ctx.source.nextId
74-
7573
if tree.symbol.denot != SymDenotations.NoDenotation
7674
&& tree.symbol.owner.companionModule == defn.CompiletimeTestingPackageObject
7775
then
@@ -136,10 +134,6 @@ object Inliner {
136134
|You can use ${setting.name} to change the limit.""",
137135
(tree :: enclosingInlineds).last.srcPos
138136
)
139-
140-
val endId = ctx.source.nextId
141-
addInlinedTrees(endId - startId)
142-
143137
tree2
144138
}
145139

@@ -735,6 +729,26 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
735729
case _ =>
736730
}
737731

732+
/** The number of nodes in this tree, excluding code in nested inline
733+
* calls and annotations of definitions.
734+
*/
735+
def treeSize(x: Any): Int =
736+
var siz = 0
737+
x match
738+
case x: Trees.Inlined[_] =>
739+
case x: Positioned =>
740+
var i = 0
741+
while i < x.productArity do
742+
siz += treeSize(x.productElement(i))
743+
i += 1
744+
case x: List[_] =>
745+
var xs = x
746+
while xs.nonEmpty do
747+
siz += treeSize(xs.head)
748+
xs = xs.tail
749+
case _ =>
750+
siz
751+
738752
trace(i"inlining $call", inlining, show = true) {
739753

740754
// The normalized bindings collected in `bindingsBuf`
@@ -758,6 +772,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
758772

759773
if (inlinedMethod == defn.Compiletime_error) issueError()
760774

775+
addInlinedTrees(treeSize(finalExpansion))
776+
761777
// Take care that only argument bindings go into `bindings`, since positions are
762778
// different for bindings from arguments and bindings from body.
763779
tpd.Inlined(call, finalBindings, finalExpansion)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package dotty.tools.dotc.util
2+
3+
/** A dense map from some `Key` type to `Int. Dense means: All keys and values
4+
* are stored in arrays from 0 up to the size of the map. Keys and values
5+
* can be obtained by index using `key(index)` and `value(index)`. Values
6+
* can also be stored using `setValue(index, value)`.
7+
*
8+
* ome privileged protected access to its internals
9+
* @param initialCapacity Indicates the initial number of slots in the hash table.
10+
* The actual number of slots is always a power of 2, so the
11+
* initial size of the table will be the smallest power of two
12+
* that is equal or greater than the given `initialCapacity`.
13+
* Minimum value is 4.
14+
* @param capacityMultiple The minimum multiple of capacity relative to used elements.
15+
* The hash table will be re-sized once the number of elements
16+
* multiplied by capacityMultiple exceeds the current size of the hash table.
17+
* However, a table of size up to DenseLimit will be re-sized only
18+
* once the number of elements reaches the table's size.
19+
*/
20+
final class IntMap[Key](initialCapacity: Int = 8, capacityMultiple: Int = 2)
21+
extends PerfectHashing[Key](initialCapacity, capacityMultiple):
22+
private var values: Array[Int] = _
23+
24+
def default: Int = -1
25+
26+
protected override def allocate(capacity: Int) =
27+
super.allocate(capacity)
28+
values = new Array[Int](capacity)
29+
30+
/** The value associated with key `k`, or else `default`. */
31+
def apply(k: Key): Int =
32+
val idx = index(k)
33+
if idx < 0 then default else values(idx)
34+
35+
/** Associate key `k` with value `v` */
36+
def update(k: Key, v: Int): Unit =
37+
val idx = add(k) // don't merge the two statements, `add` might change `values`.
38+
values(idx) = v
39+
40+
protected override def growTable() =
41+
val oldValues = values
42+
super.growTable()
43+
Array.copy(oldValues, 0, values, 0, oldValues.length)
44+
45+
def valuesIterator = values.iterator.take(size)
46+
47+
def iterator: Iterator[(Key, Int)] = keysIterator.zip(valuesIterator)
48+
49+
/** The value stored at index `i` */
50+
def value(i: Int) = values(i)
51+
52+
/** Change the value stored at index `i` to `v` */
53+
def setValue(i: Int, v: Int) = values(i) = v
54+
55+
override def toString =
56+
iterator.map((k, v) => s"$k -> $v").mkString("IntMap(", ", ", ")")
57+
end IntMap
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package dotty.tools.dotc.util
2+
3+
object PerfectHashing:
4+
5+
/** The number of elements up to which dense packing is used.
6+
* If the number of elements reaches `DenseLimit` a hash table is used instead
7+
*/
8+
inline val DenseLimit = 16
9+
10+
/** A map that maps keys to unique integers in a dense interval starting at 0.
11+
* @param initialCapacity Indicates the initial number of slots in the hash table.
12+
* The actual number of slots is always a power of 2, so the
13+
* initial size of the table will be the smallest power of two
14+
* that is equal or greater than the given `initialCapacity`.
15+
* Minimum value is 4.
16+
* @param capacityMultiple The minimum multiple of capacity relative to used elements.
17+
* The hash table will be re-sized once the number of elements
18+
* multiplied by capacityMultiple exceeds the current size of the hash table.
19+
* However, a table of size up to DenseLimit will be re-sized only
20+
* once the number of elements reaches the table's size.
21+
*/
22+
class PerfectHashing[Key](initialCapacity: Int = 8, capacityMultiple: Int = 2):
23+
import PerfectHashing.DenseLimit
24+
25+
private var used: Int = _
26+
private var table: Array[Int] = _
27+
private var keys: Array[AnyRef] = _
28+
29+
clear()
30+
31+
protected def allocate(capacity: Int) =
32+
keys = new Array[AnyRef](capacity)
33+
if !isDense then
34+
table = new Array[Int](capacity * roundToPower(capacityMultiple))
35+
36+
private def roundToPower(n: Int) =
37+
if Integer.bitCount(n) == 1 then n
38+
else 1 << (32 - Integer.numberOfLeadingZeros(n))
39+
40+
/** Remove keys from this map and set back to initial configuration */
41+
def clear(): Unit =
42+
used = 0
43+
allocate(roundToPower(initialCapacity max 4))
44+
45+
/** The number of keys */
46+
final def size: Int = used
47+
48+
/** The number of keys that can be stored without growing the tables */
49+
final def capacity: Int = keys.length
50+
51+
private final def isDense = capacity <= DenseLimit
52+
53+
/** Hashcode, by default a post-processed versoon of `k.hashCode`,
54+
* can be overridden
55+
*/
56+
protected def hash(k: Key): Int =
57+
val h = k.hashCode
58+
// Part of the MurmurHash3 32 bit finalizer
59+
val i = (h ^ (h >>> 16)) * 0x85EBCA6B
60+
val j = (i ^ (i >>> 13)) & 0x7FFFFFFF
61+
if (j==0) 0x41081989 else j
62+
63+
/** Equality test, by default `equals`, can be overridden */
64+
protected def isEqual(x: Key, y: Key): Boolean = x.equals(y)
65+
66+
private def matches(entry: Int, k: Key) = isEqual(key(entry), k)
67+
68+
private def tableIndex(x: Int): Int = x & (table.length - 1)
69+
private def firstIndex(k: Key) = tableIndex(hash(k))
70+
private def nextIndex(idx: Int) = tableIndex(idx + 1)
71+
72+
/** The key at index `idx` */
73+
def key(idx: Int) = keys(idx).asInstanceOf[Key]
74+
private def setKey(e: Int, k: Key) = keys(e) = k.asInstanceOf[AnyRef]
75+
76+
private def entry(idx: Int): Int = table(idx) - 1
77+
private def setEntry(idx: Int, entry: Int) = table(idx) = entry + 1
78+
79+
/** An index `idx` such that `key(idx) == k`, or -1 if no such index exists */
80+
def index(k: Key): Int =
81+
if isDense then
82+
var e = 0
83+
while e < used do
84+
if matches(e, k) then return e
85+
e += 1
86+
-1
87+
else
88+
var idx = firstIndex(k)
89+
var e = entry(idx)
90+
while e >= 0 && !matches(e, k) do
91+
idx = nextIndex(idx)
92+
e = entry(idx)
93+
e
94+
95+
/** An index `idx` such that key(idx) == k.
96+
* If no such index exists, create an entry with an index one
97+
* larger than the previous one.
98+
*/
99+
def add(k: Key): Int =
100+
if isDense then
101+
var e = 0
102+
while e < used do
103+
if matches(e, k) then return e
104+
e += 1
105+
else
106+
var idx = firstIndex(k)
107+
var e = entry(idx)
108+
while e >= 0 do
109+
if matches(e, k) then return e
110+
idx = nextIndex(idx)
111+
e = entry(idx)
112+
setEntry(idx, used)
113+
end if
114+
setKey(used, k)
115+
used = used + 1
116+
if used == capacity then growTable()
117+
used - 1
118+
119+
private def rehash(): Unit =
120+
var e = 0
121+
while e < used do
122+
var idx = firstIndex(key(e))
123+
while entry(idx) >= 0 do idx = nextIndex(idx)
124+
setEntry(idx, e)
125+
e += 1
126+
127+
/** Grow backing arrays */
128+
protected def growTable(): Unit =
129+
val oldKeys = keys
130+
allocate(capacity * 2)
131+
Array.copy(oldKeys, 0, keys, 0, oldKeys.length)
132+
if !isDense then rehash()
133+
134+
def keysIterator: Iterator[Key] =
135+
keys.iterator.take(used).asInstanceOf[Iterator[Key]]
136+
end PerfectHashing

0 commit comments

Comments
 (0)