Skip to content

Commit a17e2b0

Browse files
committed
Extract reusable code
1 parent d99d9bf commit a17e2b0

File tree

5 files changed

+394
-323
lines changed

5 files changed

+394
-323
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package dotty.tools.dotc
2+
package transform
3+
package init
4+
5+
import core.*
6+
import Contexts.*
7+
8+
import ast.tpd
9+
import tpd.Tree
10+
11+
class Cache[Config, Res]:
12+
import Cache.*
13+
14+
/** The cache for expression values from last iteration */
15+
protected var last: ExprValueCache[Config, Res] = Map.empty
16+
17+
/** The output cache for expression values
18+
*
19+
* The output cache is computed based on the cache values `last` from the
20+
* last iteration.
21+
*
22+
* Both `last` and `current` are required to make sure an encountered
23+
* expression is evaluated once in each iteration.
24+
*/
25+
protected var current: ExprValueCache[Config, Res] = Map.empty
26+
27+
/** Whether the current heap is different from the last heap?
28+
*
29+
* `changed == false` implies that the fixed point has been reached.
30+
*/
31+
protected var changed: Boolean = false
32+
33+
/** Used to avoid allocation, its state does not matter */
34+
protected given MutableTreeWrapper = new MutableTreeWrapper
35+
36+
def get(config: Config, expr: Tree): Option[Res] =
37+
current.get(config, expr)
38+
39+
/** Copy the value (config, expr)` from the last cache to the current cache
40+
*
41+
* It assumes `default` if it doesn't exist in the last cache.
42+
*
43+
* It updates the current caches if the values change.
44+
*
45+
* The two caches are required because we want to make sure in a new iteration, an expression is evaluated once.
46+
*/
47+
def cachedEval(config: Config, expr: Tree, cacheResult: Boolean, default: Res)(eval: => Res): Res =
48+
this.get(config, expr) match
49+
case Some(value) => value
50+
case None =>
51+
val assumeValue: Res =
52+
last.get(config, expr) match
53+
case Some(value) => value
54+
case None =>
55+
this.last = last.updatedNested(config, expr, default)
56+
default
57+
58+
this.current = current.updatedNested(config, expr, assumeValue)
59+
60+
val actual = eval
61+
if actual != assumeValue then
62+
// println("Changed! from = " + assumeValue + ", to = " + actual)
63+
this.changed = true
64+
// TODO: respect cacheResult to reduce cache size
65+
this.current = this.current.updatedNested(config, expr, actual)
66+
// this.current = this.current.removed(config, expr)
67+
end if
68+
69+
actual
70+
end cachedEval
71+
72+
def hasChanged = changed
73+
74+
/** Prepare cache for the next iteration
75+
*
76+
* 1. Reset changed flag.
77+
*
78+
* 2. Use current cache as last cache and set current cache to be empty.
79+
*/
80+
def prepareForNextIteration()(using Context) =
81+
this.changed = false
82+
this.last = this.current
83+
this.current = Map.empty
84+
end Cache
85+
86+
object Cache:
87+
type ExprValueCache[Config, Res] = Map[Config, Map[TreeWrapper, Res]]
88+
89+
/** A wrapper for trees for storage in maps based on referential equality of trees. */
90+
abstract class TreeWrapper:
91+
def tree: Tree
92+
93+
override final def equals(other: Any): Boolean =
94+
other match
95+
case that: TreeWrapper => this.tree eq that.tree
96+
case _ => false
97+
98+
override final def hashCode = tree.hashCode
99+
100+
/** The immutable wrapper is intended to be stored as key in the heap. */
101+
class ImmutableTreeWrapper(val tree: Tree) extends TreeWrapper
102+
103+
/** For queries on the heap, reuse the same wrapper to avoid unnecessary allocation.
104+
*
105+
* A `MutableTreeWrapper` is only ever used temporarily for querying a map,
106+
* and is never inserted to the map.
107+
*/
108+
class MutableTreeWrapper extends TreeWrapper:
109+
var queryTree: Tree | Null = null
110+
def tree: Tree = queryTree match
111+
case tree: Tree => tree
112+
case null => ???
113+
114+
extension [Config, Res](cache: ExprValueCache[Config, Res])
115+
def get(config: Config, expr: Tree)(using queryWrapper: MutableTreeWrapper): Option[Res] =
116+
queryWrapper.queryTree = expr
117+
cache.get(config).flatMap(_.get(queryWrapper))
118+
119+
def removed(config: Config, expr: Tree)(using queryWrapper: MutableTreeWrapper) =
120+
queryWrapper.queryTree = expr
121+
val innerMap2 = cache(config).removed(queryWrapper)
122+
cache.updated(config, innerMap2)
123+
124+
def updatedNested(config: Config, expr: Tree, result: Res): ExprValueCache[Config, Res] =
125+
val wrapper = new ImmutableTreeWrapper(expr)
126+
updatedNestedWrapper(config, wrapper, result)
127+
128+
def updatedNestedWrapper(config: Config, wrapper: ImmutableTreeWrapper, result: Res): ExprValueCache[Config, Res] =
129+
val innerMap = cache.getOrElse(config, Map.empty[TreeWrapper, Res])
130+
val innerMap2 = innerMap.updated(wrapper, result)
131+
cache.updated(config, innerMap2)
132+
end extension

compiler/src/dotty/tools/dotc/transform/init/Errors.scala

Lines changed: 13 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,106 +5,61 @@ package init
55

66
import ast.tpd._
77
import core._
8-
import util.SourcePosition
98
import util.Property
10-
import Decorators._, printing.SyntaxHighlighting
9+
import util.SourcePosition
1110
import Types._, Symbols._, Contexts._
1211

13-
import scala.collection.mutable
12+
import Trace.Trace
1413

1514
object Errors:
1615
private val IsFromPromotion = new Property.Key[Boolean]
1716

1817
sealed trait Error:
19-
def trace: Seq[Tree]
18+
def trace: Trace
2019
def show(using Context): String
2120

22-
def pos(using Context): SourcePosition = trace.last.sourcePos
21+
def pos(using Context): SourcePosition = Trace.position(using trace).sourcePos
2322

2423
def stacktrace(using Context): String =
2524
val preamble: String =
2625
if ctx.property(IsFromPromotion).nonEmpty
2726
then " Promotion trace:\n"
2827
else " Calling trace:\n"
29-
buildStacktrace(trace, preamble)
28+
Trace.buildStacktrace(trace, preamble)
3029

3130
def issue(using Context): Unit =
3231
report.warning(show, this.pos)
3332
end Error
3433

35-
def buildStacktrace(trace: Seq[Tree], preamble: String)(using Context): String = if trace.isEmpty then "" else preamble + {
36-
var lastLineNum = -1
37-
var lines: mutable.ArrayBuffer[String] = new mutable.ArrayBuffer
38-
trace.foreach { tree =>
39-
val pos = tree.sourcePos
40-
val prefix = "-> "
41-
val line =
42-
if pos.source.exists then
43-
val loc = "[ " + pos.source.file.name + ":" + (pos.line + 1) + " ]"
44-
val code = SyntaxHighlighting.highlight(pos.lineContent.trim.nn)
45-
i"$code\t$loc"
46-
else
47-
tree.show
48-
val positionMarkerLine =
49-
if pos.exists && pos.source.exists then
50-
positionMarker(pos)
51-
else ""
52-
53-
// always use the more precise trace location
54-
if lastLineNum == pos.line then
55-
lines.dropRightInPlace(1)
56-
57-
lines += (prefix + line + "\n" + positionMarkerLine)
58-
59-
lastLineNum = pos.line
60-
}
61-
val sb = new StringBuilder
62-
for line <- lines do sb.append(line)
63-
sb.toString
64-
}
65-
66-
/** Used to underline source positions in the stack trace
67-
* pos.source must exist
68-
*/
69-
private def positionMarker(pos: SourcePosition): String =
70-
val trimmed = pos.lineContent.takeWhile(c => c.isWhitespace).length
71-
val padding = pos.startColumnPadding.substring(trimmed).nn + " "
72-
val carets =
73-
if (pos.startLine == pos.endLine)
74-
"^" * math.max(1, pos.endColumn - pos.startColumn)
75-
else "^"
76-
77-
s"$padding$carets\n"
78-
7934
override def toString() = this.getClass.getName.nn
8035

8136
/** Access non-initialized field */
82-
case class AccessNonInit(field: Symbol)(val trace: Seq[Tree]) extends Error:
83-
def source: Tree = trace.last
37+
case class AccessNonInit(field: Symbol)(val trace: Trace) extends Error:
38+
def source: Tree = Trace.position(using trace)
8439
def show(using Context): String =
8540
"Access non-initialized " + field.show + "." + stacktrace
8641

8742
override def pos(using Context): SourcePosition = field.sourcePos
8843

8944
/** Promote a value under initialization to fully-initialized */
90-
case class PromoteError(msg: String)(val trace: Seq[Tree]) extends Error:
45+
case class PromoteError(msg: String)(val trace: Trace) extends Error:
9146
def show(using Context): String = msg + stacktrace
9247

93-
case class AccessCold(field: Symbol)(val trace: Seq[Tree]) extends Error:
48+
case class AccessCold(field: Symbol)(val trace: Trace) extends Error:
9449
def show(using Context): String =
9550
"Access field " + field.show + " on a cold object." + stacktrace
9651

97-
case class CallCold(meth: Symbol)(val trace: Seq[Tree]) extends Error:
52+
case class CallCold(meth: Symbol)(val trace: Trace) extends Error:
9853
def show(using Context): String =
9954
"Call method " + meth.show + " on a cold object." + stacktrace
10055

101-
case class CallUnknown(meth: Symbol)(val trace: Seq[Tree]) extends Error:
56+
case class CallUnknown(meth: Symbol)(val trace: Trace) extends Error:
10257
def show(using Context): String =
10358
val prefix = if meth.is(Flags.Method) then "Calling the external method " else "Accessing the external field"
10459
prefix + meth.show + " may cause initialization errors." + stacktrace
10560

10661
/** Promote a value under initialization to fully-initialized */
107-
case class UnsafePromotion(msg: String, error: Error)(val trace: Seq[Tree]) extends Error:
62+
case class UnsafePromotion(msg: String, error: Error)(val trace: Trace) extends Error:
10863
def show(using Context): String =
10964
msg + stacktrace + "\n" +
11065
"Promoting the value to hot (transitively initialized) failed due to the following problem:\n" + {
@@ -116,7 +71,7 @@ object Errors:
11671
*
11772
* Invariant: argsIndices.nonEmpty
11873
*/
119-
case class UnsafeLeaking(error: Error, nonHotOuterClass: Symbol, argsIndices: List[Int])(val trace: Seq[Tree]) extends Error:
74+
case class UnsafeLeaking(error: Error, nonHotOuterClass: Symbol, argsIndices: List[Int])(val trace: Trace) extends Error:
12075
def show(using Context): String =
12176
"Problematic object instantiation: " + argumentInfo() + stacktrace + "\n" +
12277
"It leads to the following error during object initialization:\n" +

0 commit comments

Comments
 (0)