Skip to content

Standardize on log-based undo #23357

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 46 additions & 95 deletions compiler/src/dotty/tools/dotc/cc/CaptureSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,7 @@ sealed abstract class CaptureSet extends Showable:

/** Try to include all element in `refs` to this capture set. */
protected final def tryInclude(newElems: Refs, origin: CaptureSet)(using Context, VarState): Boolean =
TypeComparer.inNestedLevel:
// Run in nested level so that a error notes for a failure here can be
// cancelled in case the whole comparison succeeds.
// We do this here because all nested tryInclude and subCaptures calls go
// through this method.
newElems.forall(tryInclude(_, origin))
newElems.forall(tryInclude(_, origin))

protected def mutableToReader(origin: CaptureSet)(using Context): Boolean =
if mutability == Mutable then toReader() else true
Expand Down Expand Up @@ -284,16 +279,14 @@ sealed abstract class CaptureSet extends Showable:

/** The subcapturing test, using a given VarState */
final def subCaptures(that: CaptureSet)(using ctx: Context, vs: VarState = VarState()): Boolean =
val this1 = this.adaptMutability(that)
if this1 == null then false
else if this1 ne this then
capt.println(i"WIDEN ro $this with ${this.mutability} <:< $that with ${that.mutability} to $this1")
this1.subCaptures(that, vs)
else if that.tryInclude(elems, this) then
addDependent(that)
else
varState.rollBack()
false
TypeComparer.inNestedLevel:
val this1 = this.adaptMutability(that)
if this1 == null then false
else if this1 ne this then
capt.println(i"WIDEN ro $this with ${this.mutability} <:< $that with ${that.mutability} to $this1")
this1.subCaptures(that, vs)
else
that.tryInclude(elems, this) && addDependent(that)

/** Two capture sets are considered =:= equal if they mutually subcapture each other
* in a frozen state.
Expand Down Expand Up @@ -662,30 +655,6 @@ object CaptureSet:

var description: String = ""

/** Record current elements in given VarState provided it does not yet
* contain an entry for this variable.
*/
private def recordElemsState()(using VarState): Boolean =
varState.getElems(this) match
case None => varState.putElems(this, elems)
case _ => true

/** Record current dependent sets in given VarState provided it does not yet
* contain an entry for this variable.
*/
private[CaptureSet] def recordDepsState()(using VarState): Boolean =
varState.getDeps(this) match
case None => varState.putDeps(this, deps)
case _ => true

/** Reset elements to what was recorded in `state` */
def resetElems()(using state: VarState): Unit =
elems = state.elems(this)

/** Reset dependent sets to what was recorded in `state` */
def resetDeps()(using state: VarState): Unit =
deps = state.deps(this)

/** Check that all maps recorded in skippedMaps map `elem` to itself
* or something subsumed by it.
*/
Expand All @@ -695,16 +664,28 @@ object CaptureSet:
assert(elem.subsumes(elem1),
i"Skipped map ${tm.getClass} maps newly added $elem to $elem1 in $this")

protected def includeElem(elem: Capability)(using Context): Unit =
if !elems.contains(elem) then
elems += elem
TypeComparer.logUndoAction: () =>
elems -= elem

def includeDep(cs: CaptureSet)(using Context): Unit =
if !deps.contains(cs) then
deps += cs
TypeComparer.logUndoAction: () =>
deps -= cs

final def addThisElem(elem: Capability)(using Context, VarState): Boolean =
if isConst || !recordElemsState() then // Fail if variable is solved or given VarState is frozen
if isConst || !varState.canRecord then // Fail if variable is solved or given VarState is frozen
addIfHiddenOrFail(elem)
else if !levelOK(elem) then
failWith(IncludeFailure(this, elem, levelError = true)) // or `elem` is not visible at the level of the set.
else
// id == 108 then assert(false, i"trying to add $elem to $this")
assert(elem.isWellformed, elem)
assert(!this.isInstanceOf[HiddenSet] || summon[VarState].isSeparating, summon[VarState])
elems += elem
includeElem(elem)
if isBadRoot(rootLimit, elem) then
rootAddedHandler()
newElemAddedHandler(elem)
Expand Down Expand Up @@ -772,7 +753,7 @@ object CaptureSet:
(cs eq this)
|| cs.isUniversal
|| isConst
|| recordDepsState() && { deps += cs; true }
|| varState.canRecord && { includeDep(cs); true }

override def disallowRootCapability(upto: Symbol)(handler: () => Context ?=> Unit)(using Context): this.type =
rootLimit = upto
Expand Down Expand Up @@ -1126,7 +1107,7 @@ object CaptureSet:
if alias ne this then alias.add(elem)
else
def addToElems() =
elems += elem
includeElem(elem)
deps.foreach: dep =>
assert(dep != this)
vs.addHidden(dep.asInstanceOf[HiddenSet], elem)
Expand All @@ -1142,7 +1123,7 @@ object CaptureSet:
deps = SimpleIdentitySet(elem.hiddenSet)
else
addToElems()
elem.hiddenSet.deps += this
elem.hiddenSet.includeDep(this)
case _ =>
addToElems()

Expand Down Expand Up @@ -1238,41 +1219,15 @@ object CaptureSet:
*/
class VarState:

/** A map from captureset variables to their elements at the time of the snapshot. */
private val elemsMap: util.EqHashMap[Var, Refs] = new util.EqHashMap

/** A map from captureset variables to their dependent sets at the time of the snapshot. */
private val depsMap: util.EqHashMap[Var, Deps] = new util.EqHashMap

/** A map from ResultCap values to other ResultCap values. If two result values
* `a` and `b` are unified, then `eqResultMap(a) = b` and `eqResultMap(b) = a`.
*/
private var eqResultMap: util.SimpleIdentityMap[ResultCap, ResultCap] = util.SimpleIdentityMap.empty

/** A snapshot of the `eqResultMap` value at the start of a VarState transaction */
private var eqResultSnapshot: util.SimpleIdentityMap[ResultCap, ResultCap] | Null = null

/** The recorded elements of `v` (it's required that a recording was made) */
def elems(v: Var): Refs = elemsMap(v)

/** Optionally the recorded elements of `v`, None if nothing was recorded for `v` */
def getElems(v: Var): Option[Refs] = elemsMap.get(v)

/** Record elements, return whether this was allowed.
* By default, recording is allowed in regular but not in frozen states.
*/
def putElems(v: Var, elems: Refs): Boolean = { elemsMap(v) = elems; true }

/** The recorded dependent sets of `v` (it's required that a recording was made) */
def deps(v: Var): Deps = depsMap(v)

/** Optionally the recorded dependent sets of `v`, None if nothing was recorded for `v` */
def getDeps(v: Var): Option[Deps] = depsMap.get(v)

/** Record dependent sets, return whether this was allowed.
* By default, recording is allowed in regular but not in frozen states.
*/
def putDeps(v: Var, deps: Deps): Boolean = { depsMap(v) = deps; true }
def canRecord: Boolean = true

/** Does this state allow additions of elements to capture set variables? */
def isOpen = true
Expand All @@ -1283,11 +1238,6 @@ object CaptureSet:
* but the special state VarState.Separate overrides this.
*/
def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean =
elemsMap.get(hidden) match
case None =>
elemsMap(hidden) = hidden.elems
depsMap(hidden) = hidden.deps
case _ =>
hidden.add(elem)(using ctx, this)
true

Expand All @@ -1311,17 +1261,13 @@ object CaptureSet:
&& eqResultMap(c1) == null
&& eqResultMap(c2) == null
&& {
if eqResultSnapshot == null then eqResultSnapshot = eqResultMap
eqResultMap = eqResultMap.updated(c1, c2).updated(c2, c1)
TypeComparer.logUndoAction: () =>
eqResultMap.remove(c1)
eqResultMap.remove(c2)
true
}

/** Roll back global state to what was recorded in this VarState */
def rollBack(): Unit =
elemsMap.keysIterator.foreach(_.resetElems()(using this))
depsMap.keysIterator.foreach(_.resetDeps()(using this))
if eqResultSnapshot != null then eqResultMap = eqResultSnapshot.nn

private var seen: util.EqHashSet[Capability] = new util.EqHashSet

/** Run test `pred` unless `ref` was seen in an enclosing `ifNotSeen` operation */
Expand All @@ -1341,8 +1287,7 @@ object CaptureSet:
* subsume arbitary types, which are then recorded in their hidden sets.
*/
class Closed extends VarState:
override def putElems(v: Var, refs: Refs) = false
override def putDeps(v: Var, deps: Deps) = false
override def canRecord = false
override def isOpen = false
override def toString = "closed varState"

Expand All @@ -1366,23 +1311,29 @@ object CaptureSet:
*/
def HardSeparate(using Context): Separating = ccState.HardSeparate

/** A special state that turns off recording of elements. Used only
* in `addSub` to prevent cycles in recordings. Instantiated in ccState.Unrecorded.
*/
class Unrecorded extends VarState:
override def putElems(v: Var, refs: Refs) = true
override def putDeps(v: Var, deps: Deps) = true
override def rollBack(): Unit = ()
/** A mixin trait that overrides the addHidden and unify operations to
* not depend in state. */
trait Stateless extends VarState:

/** Allow adding hidden elements, but don't store them */
override def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean = true

/** Don't allow to unify result caps */
override def unify(c1: ResultCap, c2: ResultCap)(using Context): Boolean = false
end Stateless

/** An open state that turns off recording of hidden elements (but allows
* adding them). Used in `addAsDependentTo`. Instantiated in ccState.Unrecorded.
*/
class Unrecorded extends VarState, Stateless:
override def toString = "unrecorded varState"

def Unrecorded(using Context): Unrecorded = ccState.Unrecorded

/** A closed state that turns off recording of hidden elements (but allows
* adding them). Used in `mightAccountFor`. Instantiated in ccState.ClosedUnrecorded.
*/
class ClosedUnrecorded extends Closed:
override def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean = true
class ClosedUnrecorded extends Closed, Stateless:
override def toString = "closed unrecorded varState"

def ClosedUnrecorded(using Context): ClosedUnrecorded = ccState.ClosedUnrecorded
Expand Down
9 changes: 5 additions & 4 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
needsGc = false
maxErrorLevel = -1
errorNotes = Nil
logSize = 0
undoLog.clear()
if Config.checkTypeComparerReset then checkReset()

private var pendingSubTypes: util.MutableSet[(Type, Type)] | Null = null
Expand All @@ -63,8 +63,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
private var maxErrorLevel: Int = -1
protected var errorNotes: List[(Int, ErrorNote)] = Nil

private val undoLog = mutable.ArrayBuffer[() => Unit]()
private var logSize = 0
val undoLog = mutable.ArrayBuffer[() => Unit]()

private var needsGc = false

Expand Down Expand Up @@ -1588,18 +1587,20 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
undoLog(i)()
i += 1
undoLog.takeInPlace(prevSize)
assert(undoLog.size == prevSize)

// begin recur
if tp2 eq NoType then false
else if tp1 eq tp2 then true
else
val savedCstr = constraint
val savedGadt = ctx.gadt
val savedLogSize = logSize
val savedLogSize = undoLog.size
inline def restore() =
state.constraint = savedCstr
ctx.gadtState.restore(savedGadt)
if undoLog.size != savedLogSize then
//println(i"ROLLBACK $tp1 <:< $tp2")
rollBack(savedLogSize)
val savedSuccessCount = successCount
try
Expand Down
7 changes: 1 addition & 6 deletions tests/neg-custom-args/captures/capt-depfun.check
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt-depfun.scala:11:43 ----------------------------------
11 | val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error // error: separatioon
11 | val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error
| ^^^^^^^
| Found: Str^{} ->{ac, y, z} Str^{y, z}
| Required: Str^{y, z} => Str^{y, z}
|
| where: => refers to a fresh root capability in the type of value dc
|
| longer explanation available when compiling with `-explain`
-- Error: tests/neg-custom-args/captures/capt-depfun.scala:11:24 -------------------------------------------------------
11 | val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error // error: separatioon
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
| Separation failure: value dc's type Str^{y, z} => Str^{y, z} hides parameters y and z.
| The parameters need to be annotated with @consume to allow this.
2 changes: 1 addition & 1 deletion tests/neg-custom-args/captures/capt-depfun.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ class Str
def f(y: Cap, z: Cap) =
def g(): C @retains[y.type | z.type] = ???
val ac: ((x: Cap) => Str @retains[x.type] => Str @retains[x.type]) = ???
val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error // error: separatioon
val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error
5 changes: 0 additions & 5 deletions tests/neg-custom-args/captures/depfun-reach.check
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,3 @@
| =>² refers to a fresh root capability in the type of value b
|
| longer explanation available when compiling with `-explain`
-- Error: tests/neg-custom-args/captures/depfun-reach.scala:18:17 ------------------------------------------------------
18 | : (xs: List[(X, () ->{io} Unit)]) => List[() ->{} Unit] = // error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|Separation failure: method foo's result type (xs: List[(X, box () ->{io} Unit)]) => List[() -> Unit] hides parameter op.
|The parameter needs to be annotated with @consume to allow this.
2 changes: 1 addition & 1 deletion tests/neg-custom-args/captures/depfun-reach.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def test(io: Object^, async: Object^) =
compose(op)

def foo[X](op: (xs: List[(X, () ->{io} Unit)]) => List[() ->{xs*} Unit])
: (xs: List[(X, () ->{io} Unit)]) => List[() ->{} Unit] = // error
: (xs: List[(X, () ->{io} Unit)]) => List[() ->{} Unit] =
op // error

def boom(op: List[(() ->{async} Unit, () ->{io} Unit)]): List[() ->{} Unit] =
Expand Down
4 changes: 2 additions & 2 deletions tests/neg-custom-args/captures/effect-swaps-explicit.check
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:69:10 ------------------------
69 | Future: fut ?=> // error, type mismatch
| ^
|Found: (contextual$9: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]^) ?->{fr, async}
|Found: (contextual$9: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]^?) ?->{fr, async}
| box Future[box T^?]^{fr, contextual$9}
|Required: (contextual$9: boundary.Label[Result[box Future[box T^?]^?, box E^?]]^) ?=> box Future[box T^?]^?
|
Expand All @@ -32,7 +32,7 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:73:35 ------------------------
73 | Result.make[Future[T], E]: lbl ?=> // error: type mismatch
| ^
|Found: (lbl: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]^) ?->{fr, async} Future[box T^?]^{fr, lbl}
|Found: (lbl: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]^?) ?->{fr, async} Future[box T^?]^{fr, lbl}
|Required: (lbl: boundary.Label[Result[Future[T], E]]^) ?=> Future[T]
|
|where: ?=> refers to a fresh root capability created in method fail5 when checking argument to parameter body of method make
Expand Down
4 changes: 2 additions & 2 deletions tests/neg-custom-args/captures/effect-swaps.check
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps.scala:69:10 ---------------------------------
69 | Future: fut ?=> // error, type mismatch
| ^
|Found: (contextual$9: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]^{cap.rd}) ?->{fr, async}
|Found: (contextual$9: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]^?) ?->{fr, async}
| box Future[box T^?]^{fr, contextual$9}
|Required: (contextual$9: boundary.Label[Result[box Future[box T^?]^?, box E^?]]^{cap.rd}) ?=> box Future[box T^?]^?
|
Expand All @@ -32,7 +32,7 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps.scala:73:35 ---------------------------------
73 | Result.make[Future[T], E]: lbl ?=> // error: type mismatch
| ^
|Found: (lbl: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]^{cap.rd}) ?->{fr, async} Future[box T^?]^{fr, lbl}
|Found: (lbl: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]^?) ?->{fr, async} Future[box T^?]^{fr, lbl}
|Required: (lbl: boundary.Label[Result[Future[T], E]]^{cap.rd}) ?=> Future[T]
|
|where: ?=> refers to a fresh root capability created in method fail5 when checking argument to parameter body of method make
Expand Down
6 changes: 3 additions & 3 deletions tests/neg-custom-args/captures/heal-tparam-cs.check
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:10:23 -------------------------------
10 | val test1 = localCap { c => // error
| ^
|Found: (c: Capp^) ->? box () ->{c} Unit
|Found: (c: Capp^?) ->? box () ->{c} Unit
|Required: (c: Capp^) => box () ->? Unit
|
|where: => refers to a fresh root capability created in value test1 when checking argument to parameter op of method localCap
Expand All @@ -16,7 +16,7 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:15:13 -------------------------------
15 | localCap { c => // error
| ^
| Found: (x$0: Capp^) ->? () ->{x$0} Unit
| Found: (x$0: Capp^?) ->? () ->{x$0} Unit
| Required: (c: Capp^) -> () => Unit
|
| where: => refers to a root capability associated with the result type of (c: Capp^): () => Unit
Expand All @@ -31,7 +31,7 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:25:13 -------------------------------
25 | localCap { c => // error
| ^
| Found: (x$0: Capp^{io}) ->? () ->{x$0} Unit
| Found: (x$0: Capp^?) ->? () ->{x$0} Unit
| Required: (c: Capp^{io}) -> () ->{net} Unit
26 | (c1: Capp^{io}) => () => { c1.use() }
27 | }
Expand Down
Loading
Loading