Skip to content

Commit 8a9e0e9

Browse files
authored
Merge pull request #9875 from dotty-staging/optimize-applications
Optimize some application-related operations
2 parents d82b700 + 5396e71 commit 8a9e0e9

File tree

6 files changed

+106
-53
lines changed

6 files changed

+106
-53
lines changed

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,9 +1027,11 @@ object Trees {
10271027
protected def sourceFile(tree: Tree): SourceFile = tree.source
10281028

10291029
protected def finalize(tree: Tree, copied: untpd.Tree): copied.ThisTree[T] =
1030+
Stats.record(s"TreeCopier.finalize/${tree.getClass == copied.getClass}")
10301031
postProcess(tree, copied.withSpan(tree.span).withAttachmentsFrom(tree))
10311032

10321033
protected def finalize(tree: Tree, copied: untpd.MemberDef): copied.ThisTree[T] =
1034+
Stats.record(s"TreeCopier.finalize/${tree.getClass == copied.getClass}")
10331035
postProcess(tree, copied.withSpan(tree.span).withAttachmentsFrom(tree))
10341036

10351037
def Ident(tree: Tree)(name: Name)(using Context): Ident = tree match {

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,7 +1389,7 @@ class Definitions {
13891389
!tp.isInstanceOf[RefinedType]
13901390
}
13911391

1392-
/** Is `tp` a representation of a (possibly depenent) function type or an alias of such? */
1392+
/** Is `tp` a representation of a (possibly dependent) function type or an alias of such? */
13931393
def isFunctionType(tp: Type)(using Context): Boolean =
13941394
isNonRefinedFunction(tp.dropDependentRefinement)
13951395

@@ -1440,13 +1440,12 @@ class Definitions {
14401440
* - the upper bound of a TypeParamRef in the current constraint
14411441
*/
14421442
def asContextFunctionType(tp: Type)(using Context): Type =
1443-
tp.stripTypeVar.dealias match {
1443+
tp.stripTypeVar.dealias match
14441444
case tp1: TypeParamRef if ctx.typerState.constraint.contains(tp1) =>
14451445
asContextFunctionType(TypeComparer.bounds(tp1).hiBound)
14461446
case tp1 =>
1447-
if (isFunctionType(tp1) && tp1.typeSymbol.name.isContextFunction) tp1
1447+
if tp1.typeSymbol.name.isContextFunction && isFunctionType(tp1) then tp1
14481448
else NoType
1449-
}
14501449

14511450
/** Is `tp` an context function type? */
14521451
def isContextFunctionType(tp: Type)(using Context): Boolean =
@@ -1468,7 +1467,7 @@ class Definitions {
14681467
else None
14691468

14701469
def isErasedFunctionType(tp: Type)(using Context): Boolean =
1471-
isFunctionType(tp) && tp.dealias.typeSymbol.name.isErasedFunction
1470+
tp.dealias.typeSymbol.name.isErasedFunction && isFunctionType(tp)
14721471

14731472
/** A whitelist of Scala-2 classes that are known to be pure */
14741473
def isAssuredNoInits(sym: Symbol): Boolean =

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

Lines changed: 68 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -169,56 +169,89 @@ object NameOps {
169169
}
170170
}
171171

172-
def functionArity: Int =
173-
functionArityFor(str.Function) max
174-
functionArityFor(str.ContextFunction) max {
175-
val n =
176-
functionArityFor(str.ErasedFunction) max
177-
functionArityFor(str.ErasedContextFunction)
178-
if (n == 0) -1 else n
179-
}
172+
private def functionSuffixStart: Int =
173+
val first = name.firstPart
174+
var idx = first.length - 1
175+
if idx >= 8 && first(idx).isDigit then
176+
while
177+
idx = idx - 1
178+
idx >= 8 && first(idx).isDigit
179+
do ()
180+
if first(idx - 7) == 'F'
181+
&& first(idx - 6) == 'u'
182+
&& first(idx - 5) == 'n'
183+
&& first(idx - 4) == 'c'
184+
&& first(idx - 3) == 't'
185+
&& first(idx - 2) == 'i'
186+
&& first(idx - 1) == 'o'
187+
&& first(idx) == 'n'
188+
then idx - 7
189+
else -1
190+
else -1
191+
192+
/** The arity of a name ending in the suffix `Function{\d}`, but -1
193+
* if the number is larger than Int.MaxValue / 10.
194+
* @param suffixStart The index of the suffix
195+
*/
196+
private def funArity(suffixStart: Int): Int =
197+
inline val MaxSafeInt = MaxValue / 10
198+
val first = name.firstPart
199+
def collectDigits(acc: Int, idx: Int): Int =
200+
if idx == first.length then acc
201+
else
202+
val d = digit2int(first(idx), 10)
203+
if d < 0 || acc > MaxSafeInt then -1
204+
else collectDigits(acc * 10 + d, idx + 1)
205+
collectDigits(0, suffixStart + 8)
180206

181-
/** Is a function name, i.e one of FunctionXXL, FunctionN, ContextFunctionN for N >= 0 or ErasedFunctionN, ErasedContextFunctionN for N > 0
207+
/** name[0..suffixStart) == `str` */
208+
private def isPreceded(str: String, suffixStart: Int) =
209+
str.length == suffixStart && name.firstPart.startsWith(str)
210+
211+
/** Same as `funArity`, except that it returns -1 if the prefix
212+
* is not one of "", "Context", "Erased", "ErasedContext"
213+
*/
214+
private def checkedFunArity(suffixStart: Int) =
215+
if suffixStart == 0
216+
|| isPreceded("Context", suffixStart)
217+
|| isPreceded("Erased", suffixStart)
218+
|| isPreceded("ErasedContext", suffixStart)
219+
then funArity(suffixStart)
220+
else -1
221+
222+
/** Is a function name, i.e one of FunctionXXL, FunctionN, ContextFunctionN, ErasedFunctionN, ErasedContextFunctionN for N >= 0
182223
*/
183-
def isFunction: Boolean = (name eq tpnme.FunctionXXL) || functionArity >= 0
224+
def isFunction: Boolean =
225+
(name eq tpnme.FunctionXXL) || checkedFunArity(functionSuffixStart) >= 0
184226

185-
/** Is an context function name, i.e one of ContextFunctionN for N >= 0 or ErasedContextFunctionN for N > 0
227+
/** Is an context function name, i.e one of ContextFunctionN or ErasedContextFunctionN for N >= 0
186228
*/
187229
def isContextFunction: Boolean =
188-
functionArityFor(str.ContextFunction) >= 0 ||
189-
functionArityFor(str.ErasedContextFunction) > 0
230+
val suffixStart = functionSuffixStart
231+
(isPreceded("Context", suffixStart) || isPreceded("ErasedContext", suffixStart))
232+
&& funArity(suffixStart) >= 0
190233

191-
/** Is an erased function name, i.e. one of ErasedFunctionN, ErasedContextFunctionN for N > 0
234+
/** Is an erased function name, i.e. one of ErasedFunctionN, ErasedContextFunctionN for N >= 0
192235
*/
193236
def isErasedFunction: Boolean =
194-
functionArityFor(str.ErasedFunction) > 0 ||
195-
functionArityFor(str.ErasedContextFunction) > 0
237+
val suffixStart = functionSuffixStart
238+
(isPreceded("Erased", suffixStart) || isPreceded("ErasedContext", suffixStart))
239+
&& funArity(suffixStart) >= 0
196240

197241
/** Is a synthetic function name, i.e. one of
198242
* - FunctionN for N > 22
199243
* - ContextFunctionN for N >= 0
200-
* - ErasedFunctionN for N > 0
201-
* - ErasedContextFunctionN for N > 0
244+
* - ErasedFunctionN for N >= 0
245+
* - ErasedContextFunctionN for N >= 0
202246
*/
203247
def isSyntheticFunction: Boolean =
204-
functionArityFor(str.Function) > MaxImplementedFunctionArity ||
205-
functionArityFor(str.ContextFunction) >= 0 ||
206-
isErasedFunction
248+
val suffixStart = functionSuffixStart
249+
if suffixStart == 0 then funArity(suffixStart) > MaxImplementedFunctionArity
250+
else checkedFunArity(suffixStart) >= 0
207251

208-
/** Parsed function arity for function with some specific prefix */
209-
private def functionArityFor(prefix: String): Int =
210-
inline val MaxSafeInt = MaxValue / 10
211-
val first = name.firstPart
212-
def collectDigits(acc: Int, idx: Int): Int =
213-
if idx == first.length then acc
214-
else
215-
val d = digit2int(first(idx), 10)
216-
if d < 0 || acc > MaxSafeInt then -1
217-
else collectDigits(acc * 10 + d, idx + 1)
218-
if first.startsWith(prefix) && prefix.length < first.length then
219-
collectDigits(0, prefix.length)
220-
else
221-
-1
252+
def functionArity: Int =
253+
val suffixStart = functionSuffixStart
254+
if suffixStart >= 0 then checkedFunArity(suffixStart) else -1
222255

223256
/** The name of the generic runtime operation corresponding to an array operation */
224257
def genericArrayOp: TermName = name match {

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

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ object Types {
181181
* It makes no sense for it to be an alias type because isRef would always
182182
* return false in that case.
183183
*/
184-
def isRef(sym: Symbol, skipRefined: Boolean = true)(using Context): Boolean = stripAnnots.stripTypeVar match {
184+
def isRef(sym: Symbol, skipRefined: Boolean = true)(using Context): Boolean = stripped match {
185185
case this1: TypeRef =>
186186
this1.info match { // see comment in Namer#typeDefSig
187187
case TypeAlias(tp) => tp.isRef(sym, skipRefined)
@@ -196,7 +196,7 @@ object Types {
196196
case _ => false
197197
}
198198

199-
/** Is this type a (neither aliased nor applied) reference to class `sym`? */
199+
/** Is this type a (neither aliased nor applied nor annotated) reference to class `sym`? */
200200
def isDirectRef(sym: Symbol)(using Context): Boolean = stripTypeVar match {
201201
case this1: TypeRef =>
202202
this1.name == sym.name && // avoid forcing info if names differ
@@ -369,7 +369,7 @@ object Types {
369369

370370
/** Is this a match type or a higher-kinded abstraction of one?
371371
*/
372-
def isMatch(using Context): Boolean = stripTypeVar.stripAnnots match {
372+
def isMatch(using Context): Boolean = stripped match {
373373
case _: MatchType => true
374374
case tp: HKTypeLambda => tp.resType.isMatch
375375
case _ => false
@@ -1070,9 +1070,12 @@ object Types {
10701070
def stripTypeVar(using Context): Type = this
10711071

10721072
/** Remove all AnnotatedTypes wrapping this type.
1073-
*/
1073+
*/
10741074
def stripAnnots(using Context): Type = this
10751075

1076+
/** Strip TypeVars and Annotation wrappers */
1077+
def stripped(using Context): Type = this
1078+
10761079
def rewrapAnnots(tp: Type)(using Context): Type = tp.stripTypeVar match {
10771080
case AnnotatedType(tp1, annot) => AnnotatedType(rewrapAnnots(tp1), annot)
10781081
case _ => this
@@ -1098,15 +1101,23 @@ object Types {
10981101
* def o: Outer
10991102
* <o.x.type>.widen = o.C
11001103
*/
1101-
final def widen(using Context): Type = widenSingleton match {
1104+
final def widen(using Context): Type = this match
1105+
case _: TypeRef | _: MethodOrPoly => this // fast path for most frequent cases
1106+
case tp: TermRef => // fast path for next most frequent case
1107+
if tp.isOverloaded then tp else tp.underlying.widen
1108+
case tp: SingletonType => tp.underlying.widen
11021109
case tp: ExprType => tp.resultType.widen
1103-
case tp => tp
1104-
}
1110+
case tp =>
1111+
val tp1 = tp.stripped
1112+
if tp1 eq tp then tp
1113+
else
1114+
val tp2 = tp1.widen
1115+
if tp2 ne tp1 then tp2 else tp
11051116

11061117
/** Widen from singleton type to its underlying non-singleton
11071118
* base type by applying one or more `underlying` dereferences.
11081119
*/
1109-
final def widenSingleton(using Context): Type = stripTypeVar.stripAnnots match {
1120+
final def widenSingleton(using Context): Type = stripped match {
11101121
case tp: SingletonType if !tp.isOverloaded => tp.underlying.widenSingleton
11111122
case _ => this
11121123
}
@@ -4294,6 +4305,8 @@ object Types {
42944305
if (inst.exists) inst.stripTypeVar else origin
42954306
}
42964307

4308+
override def stripped(using Context): Type = stripTypeVar.stripped
4309+
42974310
/** If the variable is instantiated, its instance, otherwise its origin */
42984311
override def underlying(using Context): Type = {
42994312
val inst = instanceOpt
@@ -4697,6 +4710,8 @@ object Types {
46974710

46984711
override def stripAnnots(using Context): Type = parent.stripAnnots
46994712

4713+
override def stripped(using Context): Type = parent.stripped
4714+
47004715
private var isRefiningKnown = false
47014716
private var isRefiningCache: Boolean = _
47024717

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
693693
case _ => tp.show
694694
}
695695

696-
def refine(tp: Type): String = tp.stripAnnots.stripTypeVar match {
696+
def refine(tp: Type): String = tp.stripped match {
697697
case tp: RefinedType => refine(tp.parent)
698698
case tp: AppliedType =>
699699
refine(tp.typeConstructor) + (

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,7 +1447,7 @@ trait Applications extends Compatibility {
14471447
* that takes fewer.
14481448
*/
14491449
def compare(alt1: TermRef, alt2: TermRef)(using Context): Int = trace(i"compare($alt1, $alt2)", overload) {
1450-
record("compare")
1450+
record("resolveOverloaded.compare")
14511451

14521452
/** Is alternative `alt1` with type `tp1` as specific as alternative
14531453
* `alt2` with type `tp2` ?
@@ -1754,7 +1754,7 @@ trait Applications extends Compatibility {
17541754
*/
17551755
private def resolveOverloaded1(alts: List[TermRef], pt: Type)(using Context): List[TermRef] =
17561756
trace(i"resolve over $alts%, %, pt = $pt", typr, show = true) {
1757-
record("resolveOverloaded1")
1757+
record(s"resolveOverloaded1", alts.length)
17581758

17591759
def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty
17601760

@@ -1840,8 +1840,8 @@ trait Applications extends Compatibility {
18401840
alts.filterConserve(sizeFits(_))
18411841

18421842
def narrowByShapes(alts: List[TermRef]): List[TermRef] =
1843-
val normArgs = args.mapWithIndexConserve(normArg(alts, _, _))
1844-
if normArgs.exists(untpd.isFunctionWithUnknownParamType) then
1843+
if args.exists(untpd.isFunctionWithUnknownParamType) then
1844+
val normArgs = args.mapWithIndexConserve(normArg(alts, _, _))
18451845
if hasNamedArg(args) then narrowByTrees(alts, normArgs.map(treeShape), resultType)
18461846
else narrowByTypes(alts, normArgs.map(typeShape), resultType)
18471847
else
@@ -1859,14 +1859,17 @@ trait Applications extends Compatibility {
18591859
alts2
18601860
}
18611861

1862+
record("resolveOverloaded.FunProto", alts.length)
18621863
val alts1 = narrowBySize(alts)
18631864
//report.log(i"narrowed by size: ${alts1.map(_.symbol.showDcl)}%, %")
18641865
if isDetermined(alts1) then alts1
18651866
else
1867+
record("resolveOverloaded.narrowedBySize", alts1.length)
18661868
val alts2 = narrowByShapes(alts1)
18671869
//report.log(i"narrowed by shape: ${alts2.map(_.symbol.showDcl)}%, %")
18681870
if isDetermined(alts2) then alts2
18691871
else
1872+
record("resolveOverloaded.narrowedByShape", alts2.length)
18701873
pretypeArgs(alts2, pt)
18711874
narrowByTrees(alts2, pt.typedArgs(normArg(alts2, _, _)), resultType)
18721875

@@ -1915,6 +1918,7 @@ trait Applications extends Compatibility {
19151918
case tp: MethodType => stripImplicit(tp.resultType).isInstanceOf[MethodType]
19161919
case _ => false
19171920

1921+
record("resolveOverloaded.narrowedApplicable", candidates.length)
19181922
val found = narrowMostSpecific(candidates)
19191923
if (found.length <= 1) found
19201924
else

0 commit comments

Comments
 (0)