Skip to content

Commit 8de92b9

Browse files
committed
SepCheck: Revise definition of peaks
New definition does not go upwards from hidden to hiding, but does go into hidden sets. Except for a variant `exposedPeaks` which does not do that. This is currently very rough. There's a switch to go from old to new behavior based on source version. Todo: Polish and then drop the switch if we are convinced verything works as expected.
1 parent cd0bc94 commit 8de92b9

File tree

4 files changed

+99
-25
lines changed

4 files changed

+99
-25
lines changed

compiler/src/dotty/tools/dotc/cc/Capability.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import CCState.*
1212
import Periods.{NoRunId, RunWidth}
1313
import compiletime.uninitialized
1414
import StdNames.nme
15-
import CaptureSet.VarState
15+
import CaptureSet.{Refs, emptyRefs, VarState}
1616
import Annotations.Annotation
1717
import Flags.*
1818
import config.Printers.capt
@@ -547,6 +547,17 @@ object Capabilities:
547547
captureSetValid = currentId
548548
computed
549549

550+
/** The elements hidden by this capability, if this is a FreshCap
551+
* or a derived version of one. Read-only status is transferred from
552+
* the capability to its hidden set. TODO Should classifiers also be
553+
* transferred?
554+
*/
555+
def hiddenElems(using Context): Refs = this.stripRestricted.stripReadOnly match
556+
case self: FreshCap =>
557+
val hidden = self.hiddenSet.elems
558+
if isReadOnly then hidden.map(_.readOnly) else hidden
559+
case _ => emptyRefs
560+
550561
/** The transitive classifiers of this capability. */
551562
def transClassifiers(using Context): Classifiers =
552563
def toClassifiers(cls: ClassSymbol): Classifiers =

compiler/src/dotty/tools/dotc/cc/SepCheck.scala

Lines changed: 84 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ object SepCheck:
9898
var refs: Array[Capability] = new Array(4)
9999
var locs: Array[SrcPos] = new Array(4)
100100
var size = 0
101-
var peaks: Refs = emptyRefs
101+
var exposedPeaks: Refs = emptyRefs
102102

103103
private def double[T <: AnyRef : ClassTag](xs: Array[T]): Array[T] =
104104
val xs1 = new Array[T](xs.length * 2)
@@ -117,10 +117,10 @@ object SepCheck:
117117
if i < size then locs(i) else null
118118

119119
def clashing(ref: Capability)(using Context): SrcPos | Null =
120-
val refPeaks = ref.peaks
121-
if !peaks.sharedWith(refPeaks).isEmpty then
120+
val refPeaks = ref.exposedPeaks
121+
if !exposedPeaks.sharedWith(refPeaks).isEmpty then
122122
var i = 0
123-
while i < size && refs(i).peaks.sharedWith(refPeaks).isEmpty do
123+
while i < size && refs(i).exposedPeaks.sharedWith(refPeaks).isEmpty do
124124
i += 1
125125
assert(i < size)
126126
locs(i)
@@ -133,7 +133,7 @@ object SepCheck:
133133
refs(size) = ref
134134
locs(size) = loc
135135
size += 1
136-
peaks = peaks ++ ref.peaks
136+
exposedPeaks = exposedPeaks ++ ref.exposedPeaks
137137

138138
/** Add all references with their associated positions from `that` which
139139
* are not yet in the set.
@@ -146,14 +146,14 @@ object SepCheck:
146146
*/
147147
def segment(op: => Unit): ConsumedSet =
148148
val start = size
149-
val savedPeaks = peaks
149+
val savedPeaks = exposedPeaks
150150
try
151151
op
152152
if size == start then EmptyConsumedSet
153153
else ConstConsumedSet(refs.slice(start, size), locs.slice(start, size))
154154
finally
155155
size = start
156-
peaks = savedPeaks
156+
exposedPeaks = savedPeaks
157157
end MutConsumedSet
158158

159159
val EmptyConsumedSet = ConstConsumedSet(Array(), Array())
@@ -164,6 +164,9 @@ object SepCheck:
164164

165165
extension (refs: Refs)
166166

167+
private def peaks_(using Context): Refs =
168+
refs.filter(_.isTerminalCapability)
169+
167170
/** The footprint of a set of references `refs` the smallest set `F` such that
168171
* 1. all capabilities in `refs` satisfying (1) are in `F`
169172
* 2. if `f in F` then the footprint of `f`'s info is also in `F`.
@@ -179,6 +182,26 @@ object SepCheck:
179182
val elems: Refs = refs.filter(retain)
180183
recur(elems, elems.toList)
181184

185+
private def exposedPeaks(using Context): Refs =
186+
footPrint(followHidden = false).peaks_
187+
188+
private def footPrint(followHidden: Boolean)(using Context): Refs =
189+
def recur(seen: Refs, acc: Refs, newElems: List[Capability]): Refs = trace(i"peaks $acc, $newElems = "):
190+
newElems match
191+
case newElem :: newElems1 =>
192+
if seen.contains(newElem) then
193+
recur(seen, acc, newElems1)
194+
else newElem.stripRestricted.stripReadOnly match
195+
case _: FreshCap if !newElem.isKnownClassifiedAs(defn.Caps_SharedCapability) =>
196+
val hiddens = if followHidden then newElem.hiddenElems.toList else Nil
197+
recur(seen + newElem, acc + newElem, hiddens ++ newElems1)
198+
case _ if newElem.isTerminalCapability =>
199+
recur(seen + newElem, acc, newElems1)
200+
case _ =>
201+
recur(seen + newElem, acc, newElem.captureSetOfInfo.dropEmpties().elems.toList ++ newElems1)
202+
case Nil => acc
203+
recur(emptyRefs, emptyRefs, refs.toList)
204+
182205
private def peaks(using Context): Refs =
183206
def recur(seen: Refs, acc: Refs, newElems: List[Capability]): Refs = trace(i"peaks $acc, $newElems = "):
184207
newElems match
@@ -199,15 +222,43 @@ object SepCheck:
199222
then recur(seen + newElem, acc, newElems1)
200223
else recur(seen + newElem, acc, newElem.captureSetOfInfo.dropEmpties().elems.toList ++ newElems1)
201224
case Nil => acc
202-
recur(emptyRefs, emptyRefs, refs.toList)
225+
if ccConfig.newScheme then footPrint(followHidden = true).peaks_
226+
else recur(emptyRefs, emptyRefs, refs.toList)
203227

204-
/** The shared peaks between `refs` and `other` */
228+
/** The shared elements between `refs` and `other`.
229+
* These are the core capabilities and fresh capabilities that appear
230+
* in a (possibly classified or readOnly) version in both sets and that
231+
* that appear in a non-readOnly version in at least one of the sets.
232+
*/
205233
private def sharedWith(other: Refs)(using Context): Refs =
206234
def common(refs1: Refs, refs2: Refs) =
207-
refs1.filter: ref =>
208-
!ref.isReadOnly && refs2.exists(_.stripReadOnly eq ref)
235+
var acc: Refs = emptyRefs
236+
refs1.foreach: ref =>
237+
if !ref.isReadOnly then
238+
val coreRef = ref.stripRestricted
239+
if refs2.exists(_.stripRestricted.stripReadOnly eq coreRef) then
240+
acc += coreRef
241+
acc
209242
common(refs, other) ++ common(other, refs)
210243

244+
/** The shared peaks between `refs` and `other` */
245+
private def sharedPeaks(other: Refs)(using Context): Refs =
246+
refs.peaks_.sharedWith(other.peaks_)
247+
248+
/** Reduce a non-empty footprint set to
249+
* 1. all its non-terminial capabilities if that set is nonempty, or
250+
* 2. one of its non-hidden capabilities if one exists, or
251+
* 3. one of its capabilities.
252+
*/
253+
def reduced(using Context): Refs =
254+
assert(!refs.isEmpty)
255+
val concrete = refs.filter(!_.isTerminalCapability)
256+
if !concrete.isEmpty then concrete
257+
else
258+
val notHidden = refs -- refs.flatMap(_.hiddenElems)
259+
if !notHidden.isEmpty then SimpleIdentitySet(notHidden.nth(0))
260+
else SimpleIdentitySet(refs.nth(0))
261+
211262
/** The overlap of two footprint sets F1 and F2. This contains all exclusive references `r`
212263
* such that one of the following is true:
213264
* 1.
@@ -282,6 +333,7 @@ object SepCheck:
282333

283334
extension (ref: Capability)
284335
def peaks(using Context): Refs = SimpleIdentitySet(ref).peaks
336+
def exposedPeaks(using Context): Refs = SimpleIdentitySet(ref).exposedPeaks
285337

286338
class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
287339
import checker.*
@@ -313,7 +365,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
313365
arg.formalType.orElse(arg.nuType).deepCaptureSet.elems
314366

315367
/** The span capture set if the type of `tree` */
316-
private def captures(tree: Tree)(using Context): Refs =
368+
private def spanCaptures(tree: Tree)(using Context): Refs =
317369
tree.nuType.spanCaptureSet.elems
318370

319371
/** The deep capture set if the type of `tree` */
@@ -480,15 +532,15 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
480532
capt.println(
481533
i"""check separate $fn($args), fnCaptures = $fnCaptures,
482534
| formalCaptures = ${args.map(arg => CaptureSet(formalCaptures(arg)))},
483-
| actualCaptures = ${args.map(arg => CaptureSet(captures(arg)))},
535+
| actualCaptures = ${args.map(arg => CaptureSet(spanCaptures(arg)))},
484536
| resultPeaks = ${resultPeaks},
485537
| deps = ${deps.toList}""")
486538
val parts = qual :: args
487539
var reported: SimpleIdentitySet[Tree] = SimpleIdentitySet.empty
488540

489541
for arg <- args do
490542
val argPeaks = PeaksPair(
491-
captures(arg).peaks,
543+
spanCaptures(arg).peaks,
492544
if arg.needsSepCheck then formalCaptures(arg).hiddenSet.peaks else emptyRefs)
493545
val argDeps = deps(arg)
494546

@@ -516,7 +568,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
516568
val clashing = clashingPart(argPeaks.hidden, _.actual)
517569
if !clashing.isEmpty then
518570
if !reported.contains(clashing) then
519-
//println(i"CLASH $arg / ${argPeaks.formal} vs $clashing / ${peaksOfTree(clashing).actual} / ${captures(clashing).peaks}")
571+
//println(i"CLASH $arg / ${argPeaks.formal} vs $clashing / ${peaksOfTree(clashing).actual} / ${spanCaptures(clashing).peaks}")
520572
sepApplyError(fn, parts, arg, clashing)
521573
else assert(!argDeps.isEmpty)
522574

@@ -563,7 +615,9 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
563615

564616
for ref <- used do
565617
val pos = consumed.clashing(ref)
566-
if pos != null then consumeError(ref, pos, tree.srcPos)
618+
if pos != null then
619+
// println(i"consumed so far ${consumed.refs.toList} with peaks ${consumed.exposedPeaks.toList}, used = $used, exposed = ${ref.exposedPeaks}")
620+
consumeError(ref, pos, tree.srcPos)
567621
end checkUse
568622

569623
/** If `tp` denotes some version of a singleton capability `x.type` the set `{x, x*}`
@@ -776,7 +830,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
776830
checkConsumedRefs(toCheck, tpe, role, i"${role.description} $tpe hides", pos)
777831
case TypeRole.Argument(arg) =>
778832
if tpe.hasAnnotation(defn.ConsumeAnnot) then
779-
val capts = captures(arg).footprint
833+
val capts = spanCaptures(arg).footprint
780834
checkConsumedRefs(capts, tpe, role, i"argument to consume parameter with type ${arg.nuType} refers to", pos)
781835
case _ =>
782836

@@ -901,8 +955,8 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
901955
def checkValOrDefDef(tree: ValOrDefDef)(using Context): Unit =
902956
if !tree.symbol.isOneOf(TermParamOrAccessor) && !isUnsafeAssumeSeparate(tree.rhs) then
903957
checkType(tree.tpt, tree.symbol)
904-
capt.println(i"sep check def ${tree.symbol}: ${tree.tpt} with ${captures(tree.tpt).hiddenSet.footprint}")
905-
pushDef(tree, captures(tree.tpt).hiddenSet.deductSymRefs(tree.symbol))
958+
capt.println(i"sep check def ${tree.symbol}: ${tree.tpt} with ${spanCaptures(tree.tpt).hiddenSet.footprint}")
959+
pushDef(tree, spanCaptures(tree.tpt).hiddenSet.deductSymRefs(tree.symbol))
906960

907961
def inSection[T](op: => T)(using Context): T =
908962
val savedDefsShadow = defsShadow
@@ -918,8 +972,17 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
918972
*/
919973
def skippable(sym: Symbol)(using Context): Boolean =
920974
sym.isInlineMethod
921-
// We currently skip inline method bodies since these seem to generan
975+
// We currently skip inline method bodies since these seem to generate
922976
// spurious recheck completions. Test case is i20237.scala
977+
|| sym.is(Synthetic) && sym.name.startsWith("_")
978+
// Approximation of case class getters _1, _2, ... . We can't separation check them
979+
// or colltest5/CollectionStrawManCC5_1.scala would fail with an error in
980+
// case class Filter. TODO Investigate to decide what needs to be done
981+
// - Can we make the accessors work somehow?
982+
// - If not, should we disable just accessors or all synthetic methods?
983+
// Reporting an error in a synthetic method is very frustrating since we don't have
984+
// a position with source code to show. On the other hand, skipping all synthetic members
985+
// might cause soundness issues.
923986

924987
/** Traverse `tree` and perform separation checks everywhere */
925988
def traverse(tree: Tree)(using Context): Unit =
@@ -929,7 +992,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
929992
case tree @ Select(qual, _) if tree.symbol.is(Method) && tree.symbol.isConsumeParam =>
930993
traverseChildren(tree)
931994
checkConsumedRefs(
932-
captures(qual).footprint, qual.nuType,
995+
spanCaptures(qual).footprint, qual.nuType,
933996
TypeRole.Qualifier(qual, tree.symbol),
934997
i"call prefix of consume ${tree.symbol} refers to", qual.srcPos)
935998
case tree: GenericApply =>

tests/pos-custom-args/captures/cc-poly-varargs.scala renamed to tests/neg-custom-args/captures/cc-poly-varargs.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ def race[T, Cap^](sources: Source[T, {Cap}]^{Cap}*): Source[T, {Cap}]^{Cap} = ??
88
def either[T1, T2, Cap^](
99
src1: Source[T1, {Cap}]^{Cap},
1010
src2: Source[T2, {Cap}]^{Cap}): Source[Either[T1, T2], {Cap}]^{Cap} =
11-
val left = src1.transformValuesWith(Left(_))
12-
val right = src2.transformValuesWith(Right(_))
11+
val left = src1.transformValuesWith(Left(_)) // error
12+
val right = src2.transformValuesWith(Right(_)) // error
1313
race(left, right)

tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ object CollectionStrawMan5 {
532532
}
533533
-1
534534
}
535-
def filter(p: A => Boolean): Iterator[A]^{this, p} = new Iterator[A] {
535+
def filter(p: A ->{cap, this} Boolean): Iterator[A]^{this, p} = new Iterator[A] {
536536
private var hd: A = compiletime.uninitialized
537537
private var hdDefined: Boolean = false
538538

0 commit comments

Comments
 (0)