Skip to content

Commit 0ed90d5

Browse files
committed
Optimization: Introduce CompactAnnotation
This one takes a type argument instead of as a tree argument. For now it's reserved for retains-like annotations. CompactAnnotations don't need tree maps and annotated types containing them hash properly.
1 parent c3c121c commit 0ed90d5

File tree

8 files changed

+126
-36
lines changed

8 files changed

+126
-36
lines changed

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,16 @@ extension (tree: Tree)
6767

6868
/** The type representing the capture set of @retains, @retainsCap or @retainsByName annotation. */
6969
def retainedSet(using Context): Type =
70-
tree match
70+
val rcap = defn.RetainsCapAnnot
71+
if tree.symbol == rcap || tree.symbol.maybeOwner == rcap then
72+
defn.captureRoot.termRef
73+
else tree match
7174
case Apply(TypeApply(_, refs :: Nil), _) => refs.tpe
72-
case _ =>
73-
if tree.symbol.maybeOwner == defn.RetainsCapAnnot
74-
then defn.captureRoot.termRef else NoType
75+
case tree: TypeTree =>
76+
tree.tpe match
77+
case AppliedType(_, refs :: Nil) => refs
78+
case _ => NoType
79+
case _ => NoType
7580

7681
extension (tp: Type)
7782

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -919,7 +919,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
919919
assert(mtps.hasSameLengthAs(argss), i"diff for $fn: ${fn.symbol} /// $mtps /// $argss")
920920
val mtpsWithArgs = mtps.zip(argss)
921921
val argMap = mtpsWithArgs.toMap
922-
val deps = mutable.HashMap[Tree, List[Tree]]().withDefaultValue(Nil)
922+
val deps = mutable.LinkedHashMap[Tree, List[Tree]]().withDefaultValue(Nil)
923923

924924
def argOfDep(dep: Capability): Option[Tree] =
925925
dep.stripReach match

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ object ccConfig:
5959

6060
/** Not used currently. Handy for trying out new features */
6161
def newScheme(using ctx: Context): Boolean =
62-
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`)
62+
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.9`)
6363

6464
def allowUse(using Context): Boolean =
6565
Feature.sourceVersion.stable.isAtMost(SourceVersion.`3.7`)

compiler/src/dotty/tools/dotc/core/Annotations.scala

Lines changed: 96 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,16 @@ object Annotations {
3333
def derivedAnnotation(tree: Tree)(using Context): Annotation =
3434
if (tree eq this.tree) this else Annotation(tree)
3535

36+
def derivedClassAnnotation(cls: ClassSymbol)(using Context) =
37+
Annotation(cls, tree.span)
38+
3639
/** All term arguments of this annotation in a single flat list */
3740
def arguments(using Context): List[Tree] = tpd.allTermArguments(tree)
3841

42+
/** All type arguments of this annotation in a single flat list */
43+
def argumentTypes(using Context): List[Type] =
44+
tpd.allArguments(tree).filterConserve(_.isType).tpes
45+
3946
def argument(i: Int)(using Context): Option[Tree] = {
4047
val args = arguments
4148
if (i < args.length) Some(args(i)) else None
@@ -66,23 +73,8 @@ object Annotations {
6673
// the original tree. TODO Try to use this scheme for other annotations that
6774
// take only type arguments as well. We should wait until after 3.9 LTS to
6875
// do this, though.
69-
// 2. Map all skolems (?n: T) to (?n: Any), and map all recursive captures of
70-
// that are not on CapSet to `^`. Skolems and capturing types on types
71-
// other than CapSet are not allowed in a retains annotation anyway,
72-
// so the underlying type does not matter. This simplification prevents
73-
// exponential blowup in some cases. See i24556.scala and i24556a.scala.
74-
// 3. Drop the annotation entirely if CC is not enabled somehwere.
75-
76-
def sanitize(tp: Type): Type = tp match
77-
case SkolemType(_) =>
78-
SkolemType(defn.AnyType)
79-
case tp @ AnnotatedType(parent, ann)
80-
if ann.symbol.isRetainsLike && parent.typeSymbol != defn.Caps_CapSet =>
81-
tp.derivedAnnotatedType(parent, Annotation(defn.RetainsCapAnnot, ann.tree.span))
82-
case tp @ OrType(tp1, tp2) =>
83-
tp.derivedOrType(sanitize(tp1), sanitize(tp2))
84-
case _ =>
85-
tp
76+
// 2. Sanitize the arguments to prevent compilation time blowup.
77+
// 3. Drop the annotation entirely if CC is not enabled somewhere.
8678

8779
def rebuild(tree: Tree, mappedType: Type): Tree = tree match
8880
case Apply(fn, Nil) => cpy.Apply(tree)(rebuild(fn, mappedType), Nil)
@@ -93,8 +85,12 @@ object Annotations {
9385
EmptyAnnotation // strip retains-like annotations unless capture checking is enabled
9486
else
9587
val mappedType = sanitize(tm(arg.tpe))
96-
if mappedType `eql` arg.tpe then this
97-
else derivedAnnotation(rebuild(tree, mappedType))
88+
if mappedType `eql` arg.tpe then
89+
this
90+
else if cc.ccConfig.newScheme then
91+
CompactAnnotation(symbol.typeRef.appliedTo(mappedType))
92+
else
93+
derivedAnnotation(rebuild(tree, mappedType))
9894

9995
case args =>
10096
// Checks if `tm` would result in any change by applying it to types
@@ -114,17 +110,12 @@ object Annotations {
114110

115111
/** Does this annotation refer to a parameter of `tl`? */
116112
def refersToParamOf(tl: TermLambda)(using Context): Boolean =
117-
def isLambdaParam(t: Type) = t match
118-
case TermParamRef(tl1, _) => tl eq tl1
119-
case _ => false
120-
121113
val acc = new TreeAccumulator[Boolean]:
122114
def apply(x: Boolean, t: Tree)(using Context) =
123115
if x then true
124-
else if t.isType then
125-
t.tpe.existsPart(isLambdaParam, stopAt = StopAt.Static)
116+
else if t.isType then refersToLambdaParam(t.tpe, tl)
126117
else t match
127-
case id: (Ident | This) => isLambdaParam(id.tpe.stripped)
118+
case id: (Ident | This) => isLambdaParam(id.tpe.stripped, tl)
128119
case _ => foldOver(x, t)
129120

130121
tpd.allArguments(tree).exists(acc(false, _))
@@ -163,6 +154,82 @@ object Annotations {
163154
case class ConcreteAnnotation(t: Tree) extends Annotation:
164155
def tree(using Context): Tree = t
165156

157+
case class CompactAnnotation(tp: Type) extends Annotation:
158+
assert(tp.isInstanceOf[AppliedType | TypeRef], tp)
159+
160+
def tree(using Context) = TypeTree(tp)
161+
162+
override def symbol(using Context) = tp.typeSymbol
163+
164+
override def derivedAnnotation(tree: Tree)(using Context): Annotation =
165+
derivedAnnotation(tree.tpe)
166+
167+
override def derivedClassAnnotation(cls: ClassSymbol)(using Context) =
168+
derivedAnnotation(cls.typeRef)
169+
170+
def derivedAnnotation(tp: Type)(using Context): Annotation =
171+
if tp eq this.tp then this else CompactAnnotation(tp)
172+
173+
override def arguments(using Context): List[Tree] =
174+
argumentTypes.map(TypeTree(_))
175+
176+
override def argumentTypes(using Context): List[Type] = tp.argTypes
177+
178+
def argumentType(i: Int)(using Context): Type =
179+
val args = argumentTypes
180+
if i < args.length then args(i) else NoType
181+
182+
override def argumentConstant(i: Int)(using Context): Option[Constant] =
183+
argumentType(i).normalized match
184+
case ConstantType(c) => Some(c)
185+
case _ => None
186+
187+
override def mapWith(tm: TypeMap)(using Context): Annotation =
188+
def derived(tp: Type) =
189+
if tm.isRange(tp) then EmptyAnnotation else derivedAnnotation(tp)
190+
def sanitizeArg(tp: Type) = tp match
191+
case tp @ AppliedType(tycon, args) =>
192+
tp.derivedAppliedType(tycon, args.mapConserve(sanitize))
193+
case _ =>
194+
tp
195+
if !symbol.isRetainsLike then derived(tm(tp))
196+
else if Feature.ccEnabledSomewhere then derived(sanitizeArg(tm(tp)))
197+
else EmptyAnnotation // strip retains-like annotations unless capture checking is enabled
198+
199+
override def refersToParamOf(tl: TermLambda)(using Context): Boolean =
200+
refersToLambdaParam(tp, tl)
201+
202+
override def hash: Int = tp.hash
203+
override def eql(that: Annotation) = that match
204+
case that: CompactAnnotation => this.tp `eql` that.tp
205+
case _ => false
206+
end CompactAnnotation
207+
208+
/** Sanitize @retains arguments to approximate illegal types that could cause a compilation
209+
* time blowup before they are dropped ot detected. This means mapping all all skolems
210+
* (?n: T) to (?n: Any), and mapping all recursive captures that are not on CapSet to `^`.
211+
* Skolems and capturing types on types other than CapSet are not allowed in a
212+
* @retains annotation anyway, so the underlying type does not matter as long as it is also
213+
* illegal. See i24556.scala and i24556a.scala.
214+
*/
215+
private def sanitize(tp: Type)(using Context): Type = tp match
216+
case SkolemType(_) =>
217+
SkolemType(defn.AnyType)
218+
case tp @ AnnotatedType(parent, ann)
219+
if ann.symbol.isRetainsLike && parent.typeSymbol != defn.Caps_CapSet =>
220+
tp.derivedAnnotatedType(parent, ann.derivedClassAnnotation(defn.RetainsCapAnnot))
221+
case tp @ OrType(tp1, tp2) =>
222+
tp.derivedOrType(sanitize(tp1), sanitize(tp2))
223+
case _ =>
224+
tp
225+
226+
private def isLambdaParam(t: Type, tl: TermLambda): Boolean = t match
227+
case TermParamRef(tl1, _) => tl eq tl1
228+
case _ => false
229+
230+
private def refersToLambdaParam(tp: Type, tl: TermLambda)(using Context): Boolean =
231+
tp.existsPart(isLambdaParam(_, tl), stopAt = StopAt.Static)
232+
166233
abstract class LazyAnnotation extends Annotation {
167234
protected var mySym: Symbol | (Context ?=> Symbol) | Null
168235
override def symbol(using parentCtx: Context): Symbol =
@@ -244,7 +311,9 @@ object Annotations {
244311

245312
object Annotation {
246313

247-
def apply(tree: Tree): ConcreteAnnotation = ConcreteAnnotation(tree)
314+
def apply(tree: Tree): Annotation = tree match
315+
case tree: TypeTree => CompactAnnotation(tree.tpe)
316+
case _ => ConcreteAnnotation(tree)
248317

249318
def apply(cls: ClassSymbol, span: Span)(using Context): Annotation =
250319
apply(cls, Nil, span)

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,13 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) {
283283
}
284284
case tpe: AnnotatedType =>
285285
writeByte(ANNOTATEDtype)
286-
withLength { pickleType(tpe.parent, richTypes); pickleTree(tpe.annot.tree) }
287-
annotatedTypeTrees += tpe.annot.tree
286+
withLength:
287+
pickleType(tpe.parent, richTypes)
288+
tpe.annot match
289+
case CompactAnnotation(tp) => pickleType(tp)
290+
case _ =>
291+
pickleTree(tpe.annot.tree)
292+
annotatedTypeTrees += tpe.annot.tree
288293
case tpe: AndType =>
289294
writeByte(ANDtype)
290295
withLength { pickleType(tpe.tp1, richTypes); pickleType(tpe.tp2, richTypes) }

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,11 @@ class TreeUnpickler(reader: TastyReader,
354354
op
355355
}
356356

357+
/** Can `tag` start a type argument of a CompactAnnotation? */
358+
def isCompactAnnotTypeTag(tag: Int): Boolean = tag match
359+
case APPLIEDtype | SHAREDtype | TYPEREF | TYPEREFdirect | TYPEREFsymbol | TYPEREFin => true
360+
case _ => false
361+
357362
def readLengthType(): Type = {
358363
val end = readEnd()
359364

@@ -420,7 +425,12 @@ class TreeUnpickler(reader: TastyReader,
420425
val hi = readVariances(readType())
421426
createNullableTypeBounds(lo, hi)
422427
case ANNOTATEDtype =>
423-
AnnotatedType(readType(), Annotation(readTree()))
428+
val parent = readType()
429+
val ann =
430+
if isCompactAnnotTypeTag(nextByte)
431+
then CompactAnnotation(readType())
432+
else Annotation(readTree())
433+
AnnotatedType(parent, ann)
424434
case ANDtype =>
425435
AndType(readType(), readType())
426436
case ORtype =>

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
11651165
case Select(qual, nme.CONSTRUCTOR) => recur(qual)
11661166
case id @ Ident(tpnme.BOUNDTYPE_ANNOT) => "@" ~ toText(id.symbol.name)
11671167
case New(tpt) => recur(tpt)
1168+
case t: tpd.TypeTree if t.tpe.isInstanceOf[AppliedType] => "@" ~ toText(t.tpe)
11681169
case _ =>
11691170
val annotSym = sym.orElse(tree.symbol.enclosingClass)
11701171
if annotSym.exists then annotText(annotSym) else s"@${t.show}"
File renamed without changes.

0 commit comments

Comments
 (0)