Skip to content

Commit 849a5df

Browse files
committed
Refactor domain: Introduce Warm and ThisRef
This brings us closer to OOPSLA paper. The key insight to avoid cycles like the following ``` class A(b: B) { val b2 = new B(this) } class B(a: A) { val a2 = new A(this) } ``` is to use `Warm(C, outer)` to evaluate fields and methods of `C`. The evaluation results are cached uniformly. Therefore, there is no need to treat `Warm(C, outer)` as addresses.
1 parent dd65e1a commit 849a5df

File tree

2 files changed

+95
-99
lines changed

2 files changed

+95
-99
lines changed

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,8 @@ class Checker extends MiniPhase {
6262
// Checking.checkClassBody(tree)
6363

6464
import semantic._
65-
val addr = Addr(cls, outer = Hot)
66-
heap(addr) = Objekt(cls, Map.empty, Map(cls -> Hot))
67-
val res = addr.call(cls.primaryConstructor, superType = NoType, tree)(using ctx, Vector.empty)
65+
val thisRef = ThisRef(cls)(fields = mutable.Map.empty)
66+
val res = semantic.init(cls, thisRef)(using ctx, Vector.empty)
6867
res.errors.foreach(_.issue)
6968
}
7069

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

Lines changed: 93 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ class Semantic {
2727

2828
/** Abstract values
2929
*
30-
* Value = Hot | Cold | Addr | Fun | RefSet
31-
*
32-
* `Warm` and `This` will be addresses refer to the abstract heap
30+
* Value = Hot | Cold | Warm | ThisRef | Fun | RefSet
3331
*/
3432
trait Value {
3533
def show: String = this.toString()
@@ -41,47 +39,30 @@ class Semantic {
4139
/** An object with unknown initialization status */
4240
case object Cold extends Value
4341

44-
/** Addresses to the abstract heap
45-
*
46-
* Addresses determine abstractions of objects. Objects created
47-
* with same address are represented with the same abstraction.
48-
*
49-
* Nested addresses may lead to infinite domain, thus widen is
50-
* needed to finitize addresses. E.g. OOPSLA 2020 paper restricts
51-
* args to be either `Hot` or `Cold`
52-
*/
53-
case class Addr(klass: ClassSymbol, outer: Value) extends Value
54-
55-
/** A function value */
56-
case class Fun(expr: Tree, thisV: Addr, klass: ClassSymbol) extends Value
57-
58-
/** A value which represents a set of addresses
42+
/** Object referred by `this` which stores abstract values for all fields
5943
*
60-
* It comes from `if` expressions.
44+
* Note: the mutable `fields` plays the role of heap. Thanks to monotonicity
45+
* of the heap, we may handle it in a simple way.
6146
*/
62-
case class RefSet(refs: List[Addr | Fun]) extends Value
47+
case class ThisRef(klass: ClassSymbol)(val fields: mutable.Map[Symbol, Value]) extends Value {
48+
def updateField(field: Symbol, value: Value): Unit =
49+
fields(field) = value
50+
}
6351

64-
/** Object stores abstract values for all fields and the outer.
52+
/** An object with all fields initialized but reaches objects under initialization
6553
*
66-
* Theoretically we only need to store the outer for the concrete class,
67-
* as all other outers are determined.
68-
*
69-
* From performance reasons, we cache the immediate outer for all classes
70-
* in the inheritance hierarchy.
54+
* We need to restrict nesting levels of `outer` to finitize the domain.
7155
*/
72-
case class Objekt(klass: ClassSymbol, fields: Map[Symbol, Value], outers: Map[ClassSymbol, Value])
56+
case class Warm(klass: ClassSymbol, outer: Value) extends Value
7357

74-
/** Abstract heap stores abstract objects
75-
*
76-
* As in the OOPSLA paper, the abstract heap is monotonistic
77-
*/
78-
type Heap = mutable.Map[Addr, Objekt]
58+
/** A function value */
59+
case class Fun(expr: Tree, thisV: ThisRef | Warm, klass: ClassSymbol) extends Value
7960

80-
/** The heap for abstract objects
61+
/** A value which represents a set of addresses
8162
*
82-
* As the heap is monotonistic, we can avoid passing it around.
63+
* It comes from `if` expressions.
8364
*/
84-
val heap: Heap = mutable.Map.empty[Addr, Objekt]
65+
case class RefSet(refs: List[Warm | Fun | ThisRef]) extends Value
8566

8667
/** Interpreter configuration
8768
*
@@ -154,39 +135,50 @@ class Semantic {
154135
case (Cold, _) => Cold
155136
case (_, Cold) => Cold
156137

157-
case (a: (Fun | Addr), b: (Fun | Addr)) => RefSet(a :: b :: Nil)
138+
case (a: (Fun | Warm | ThisRef), b: (Fun | Warm | ThisRef)) => RefSet(a :: b :: Nil)
158139

159-
case (a: (Fun | Addr), RefSet(refs)) => RefSet(a :: refs)
160-
case (RefSet(refs), b: (Fun | Addr)) => RefSet(b :: refs)
140+
case (a: (Fun | Warm | ThisRef), RefSet(refs)) => RefSet(a :: refs)
141+
case (RefSet(refs), b: (Fun | Warm | ThisRef)) => RefSet(b :: refs)
161142

162143
case (RefSet(refs1), RefSet(refs2)) => RefSet(refs1 ++ refs2)
163144

164145
extension (values: Seq[Value])
165146
def join: Value = values.reduce { (v1, v2) => v1.join(v2) }
166147

167148
extension (value: Value)
168-
def select(f: Symbol, source: Tree)(using Context, Trace): Result =
149+
def select(field: Symbol, source: Tree)(using Context, Trace): Result =
169150
value match {
170151
case Hot =>
171152
Result(Hot, noErrors)
172153

173154
case Cold =>
174-
val error = AccessCold(f, source, trace)
155+
val error = AccessCold(field, source, trace)
175156
Result(Hot, error :: Nil)
176157

177-
case addr: Addr =>
178-
val obj = heap(addr)
179-
if obj.fields.contains(f) then
180-
Result(obj.fields(f), Nil)
158+
case thisRef: ThisRef =>
159+
val target = resolve(thisRef.klass, field)
160+
if target.is(Flags.Lazy) then value.call(target, superType = NoType, source)
161+
else if thisRef.fields.contains(target) then
162+
Result(thisRef.fields(target), Nil)
181163
else
182-
val error = AccessNonInit(f, trace.add(source))
164+
val error = AccessNonInit(target, trace.add(source))
165+
Result(Hot, error :: Nil)
166+
167+
case warm: Warm =>
168+
val target = resolve(warm.klass, field)
169+
if target.is(Flags.Lazy) then value.call(target, superType = NoType, source)
170+
else if target.hasSource then
171+
val rhs = target.defTree.asInstanceOf[ValDef].rhs
172+
eval(rhs, warm, target.owner.asClass)
173+
else
174+
val error = CallUnknown(field, source, trace)
183175
Result(Hot, error :: Nil)
184176

185177
case _: Fun =>
186178
???
187179

188180
case RefSet(refs) =>
189-
val resList = refs.map(_.select(f, source))
181+
val resList = refs.map(_.select(field, source))
190182
val value2 = resList.map(_.value).join
191183
val errors = resList.flatMap(_.errors)
192184
Result(value2, errors)
@@ -201,29 +193,48 @@ class Semantic {
201193
val error = CallCold(meth, source, trace)
202194
Result(Hot, error :: Nil)
203195

204-
case addr: Addr =>
205-
val obj = heap(addr)
196+
case thisRef: ThisRef =>
206197
val target =
207198
if superType.exists then
208199
// TODO: superType could be A & B when there is self-annotation
209-
resolveSuper(obj.klass, superType.classSymbol.asClass, meth)
200+
resolveSuper(thisRef.klass, superType.classSymbol.asClass, meth)
210201
else
211-
resolve(obj.klass, meth)
202+
resolve(thisRef.klass, meth)
212203
if target.isPrimaryConstructor then
213-
init(target.owner.asClass, addr)
204+
init(target.owner.asClass, thisRef)
214205
else if target.isOneOf(Flags.Method | Flags.Lazy) then
215206
if target.hasSource then
216207
val rhs = target.defTree.asInstanceOf[DefDef].rhs
217-
eval(rhs, addr, target.owner.asClass)
208+
eval(rhs, thisRef, target.owner.asClass)
218209
else
219210
val error = CallUnknown(target, source, trace)
220211
Result(Hot, error :: Nil)
212+
else if thisRef.fields.contains(target) then
213+
Result(thisRef.fields(target), Nil)
221214
else
222-
if obj.fields.contains(target) then
223-
Result(obj.fields(target), Nil)
215+
val error = AccessNonInit(target, trace.add(source))
216+
Result(Hot, error :: Nil)
217+
218+
case warm: Warm =>
219+
val target =
220+
if superType.exists then
221+
// TODO: superType could be A & B when there is self-annotation
222+
resolveSuper(warm.klass, superType.classSymbol.asClass, meth)
223+
else
224+
resolve(warm.klass, meth)
225+
if target.isOneOf(Flags.Method | Flags.Lazy) then
226+
if target.hasSource then
227+
val rhs = target.defTree.asInstanceOf[DefDef].rhs
228+
eval(rhs, warm, target.owner.asClass)
224229
else
225-
val error = AccessNonInit(target, trace.add(source))
230+
val error = CallUnknown(target, source, trace)
226231
Result(Hot, error :: Nil)
232+
else if target.hasSource then
233+
val rhs = target.defTree.asInstanceOf[ValDef].rhs
234+
eval(rhs, warm, target.owner.asClass)
235+
else
236+
val error = CallUnknown(target, source, trace)
237+
Result(Hot, error :: Nil)
227238

228239
case Fun(body, thisV, klass) =>
229240
if meth.name == nme.apply then eval(body, thisV, klass)
@@ -246,13 +257,13 @@ class Semantic {
246257
val error = CallCold(ctor, source, trace)
247258
Result(Hot, error :: Nil)
248259

249-
case addr: Addr =>
260+
case thisRef: ThisRef =>
261+
Result(Warm(klass, outer = thisRef), noErrors)
262+
263+
case warm: Warm =>
250264
// widen the outer to finitize addresses
251-
val outer = if addr.outer.isInstanceOf[Addr] then addr.copy(outer = Cold) else addr
252-
val addr2 = Addr(klass, outer)
253-
if !heap.contains(addr2) then
254-
heap(addr2) = Objekt(klass, Map.empty, Map(klass -> outer))
255-
addr2.call(ctor, superType = NoType, source)
265+
val outer = if warm.outer.isInstanceOf[Warm] then warm.copy(outer = Cold) else warm
266+
Result(Warm(klass, outer), noErrors)
256267

257268
case Fun(body, thisV, klass) =>
258269
??? // impossible
@@ -265,19 +276,6 @@ class Semantic {
265276
}
266277
end extension
267278

268-
extension (addr: Addr)
269-
def updateOuter(klass: ClassSymbol, value: Value): Unit =
270-
val obj = heap(addr)
271-
val obj2 = obj.copy(outers = obj.outers.updated(klass, value))
272-
heap(addr) = obj2
273-
274-
def updateField(field: Symbol, value: Value): Unit =
275-
val obj = heap(addr)
276-
val obj2 = obj.copy(fields = obj.fields.updated(field, value))
277-
heap(addr) = obj2
278-
end extension
279-
280-
281279
// ----- Semantic definition --------------------------------
282280

283281
/** Evaluate an expression with the given value for `this` in a given class `klass`
@@ -388,8 +386,8 @@ class Semantic {
388386

389387
case closureDef(ddef) =>
390388
thisV match
391-
case addr: Addr =>
392-
val value = Fun(ddef.rhs, addr, klass)
389+
case obj: (ThisRef | Warm) =>
390+
val value = Fun(ddef.rhs, obj, klass)
393391
Result(value, Nil)
394392
case _ =>
395393
??? // impossible
@@ -485,12 +483,7 @@ class Semantic {
485483
case tp @ ThisType(tref) =>
486484
if tref.symbol.is(Flags.Package) then Result(Hot, noErrors)
487485
else
488-
val value =
489-
thisV match
490-
case Hot => Hot
491-
case addr: Addr =>
492-
resolveThis(tp.classSymbol.asClass, addr, klass)
493-
case _ => ???
486+
val value = resolveThis(tp.classSymbol.asClass, thisV, klass)
494487
Result(value, noErrors)
495488

496489
case _: TermParamRef | _: RecThis =>
@@ -507,46 +500,50 @@ class Semantic {
507500
if target == klass then thisV
508501
else
509502
thisV match
510-
case Hot => Hot
511-
case thisV: Addr =>
512-
val outer = heap(thisV).outers.getOrElse(klass, Hot)
513-
val outerCls = klass.owner.enclosingClass.asClass
514-
resolveThis(target, outer, outerCls)
503+
case Hot | _: ThisRef => Hot
504+
case warm: Warm =>
505+
// use existing type information as a shortcut
506+
val tref = typeRefOf(warm.klass.typeRef.baseType(klass))
507+
if tref.prefix == NoPrefix then
508+
// Current class is local, in the enclosing scope of `warm.klass`
509+
val outerCls = warm.klass.owner.enclosingClass.asClass
510+
resolveThis(target, warm.outer, outerCls)
511+
else
512+
val outerCls = klass.owner.enclosingClass.asClass
513+
val res = cases(tref.prefix, warm.outer, warm.klass.owner.asClass, EmptyTree)
514+
assert(res.errors.isEmpty, "unexpected error " + res)
515+
resolveThis(target, res.value, outerCls)
515516
case _ => ???
516517
}
517518

518519
/** Compute the outer value that correspond to `tref.prefix` */
519520
def outerValue(tref: TypeRef, thisV: Value, klass: ClassSymbol, source: Tree)(using Context, Trace): Result =
520521
val cls = tref.classSymbol.asClass
521-
if (tref.prefix == NoPrefix) then
522+
if tref.prefix == NoPrefix then
522523
val enclosing = cls.owner.lexicallyEnclosingClass.asClass
523524
val outerV = resolveThis(enclosing, thisV, klass)
524525
Result(outerV, noErrors)
525526
else
526527
cases(tref.prefix, thisV, klass, source)
527528

528529
/** Initialize part of an abstract object in `klass` of the inheritance chain */
529-
def init(klass: ClassSymbol, thisV: Addr)(using Context, Trace): Result = log("init " + klass.show, printer, res => res.asInstanceOf[Result].show) {
530+
def init(klass: ClassSymbol, thisV: ThisRef)(using Context, Trace): Result = log("init " + klass.show, printer, res => res.asInstanceOf[Result].show) {
530531
val errorBuffer = new mutable.ArrayBuffer[Error]
531532

532533
val tpl = klass.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template]
533534

534535
// init param fields
535-
var obj = heap(thisV)
536536
klass.paramAccessors.foreach { acc =>
537537
if (!acc.is(Flags.Method)) {
538538
traceIndented(acc.show + " initialized", printer)
539-
obj = obj.copy(fields = obj.fields.updated(acc, Hot))
539+
thisV.updateField(acc, Hot)
540540
}
541541
}
542-
heap(thisV) = obj
543542

544543
def superCall(tref: TypeRef, ctor: Symbol, source: Tree): Unit =
545-
// update outer for super class
546544
val cls = tref.classSymbol.asClass
547-
val res = outerValue(tref, thisV, klass, source)
548-
errorBuffer ++= res.errors
549-
thisV.updateOuter(cls, res.value)
545+
// update outer for super class
546+
// ignored as they are all hot
550547

551548
// follow constructor
552549
if !cls.defTree.isEmpty then

0 commit comments

Comments
 (0)