Skip to content

Optimize some application-related operations #9875

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

Merged
merged 4 commits into from
Sep 26, 2020
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1027,9 +1027,11 @@ object Trees {
protected def sourceFile(tree: Tree): SourceFile = tree.source

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

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

def Ident(tree: Tree)(name: Name)(using Context): Ident = tree match {
Expand Down
9 changes: 4 additions & 5 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1389,7 +1389,7 @@ class Definitions {
!tp.isInstanceOf[RefinedType]
}

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

Expand Down Expand Up @@ -1440,13 +1440,12 @@ class Definitions {
* - the upper bound of a TypeParamRef in the current constraint
*/
def asContextFunctionType(tp: Type)(using Context): Type =
tp.stripTypeVar.dealias match {
tp.stripTypeVar.dealias match
case tp1: TypeParamRef if ctx.typerState.constraint.contains(tp1) =>
asContextFunctionType(TypeComparer.bounds(tp1).hiBound)
case tp1 =>
if (isFunctionType(tp1) && tp1.typeSymbol.name.isContextFunction) tp1
if tp1.typeSymbol.name.isContextFunction && isFunctionType(tp1) then tp1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the old days, we would move the condition to pattern guard in order to avoid parens. Now we will do it to avoid then.

else NoType
}

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

def isErasedFunctionType(tp: Type)(using Context): Boolean =
isFunctionType(tp) && tp.dealias.typeSymbol.name.isErasedFunction
tp.dealias.typeSymbol.name.isErasedFunction && isFunctionType(tp)

/** A whitelist of Scala-2 classes that are known to be pure */
def isAssuredNoInits(sym: Symbol): Boolean =
Expand Down
103 changes: 68 additions & 35 deletions compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -169,56 +169,89 @@ object NameOps {
}
}

def functionArity: Int =
functionArityFor(str.Function) max
functionArityFor(str.ContextFunction) max {
val n =
functionArityFor(str.ErasedFunction) max
functionArityFor(str.ErasedContextFunction)
if (n == 0) -1 else n
}
private def functionSuffixStart: Int =
val first = name.firstPart
var idx = first.length - 1
if idx >= 8 && first(idx).isDigit then
while
idx = idx - 1
idx >= 8 && first(idx).isDigit
do ()
if first(idx - 7) == 'F'
&& first(idx - 6) == 'u'
&& first(idx - 5) == 'n'
&& first(idx - 4) == 'c'
&& first(idx - 3) == 't'
&& first(idx - 2) == 'i'
&& first(idx - 1) == 'o'
&& first(idx) == 'n'
then idx - 7
else -1
else -1

/** The arity of a name ending in the suffix `Function{\d}`, but -1
* if the number is larger than Int.MaxValue / 10.
* @param suffixStart The index of the suffix
*/
private def funArity(suffixStart: Int): Int =
inline val MaxSafeInt = MaxValue / 10
val first = name.firstPart
def collectDigits(acc: Int, idx: Int): Int =
if idx == first.length then acc
else
val d = digit2int(first(idx), 10)
if d < 0 || acc > MaxSafeInt then -1
else collectDigits(acc * 10 + d, idx + 1)
collectDigits(0, suffixStart + 8)

/** Is a function name, i.e one of FunctionXXL, FunctionN, ContextFunctionN for N >= 0 or ErasedFunctionN, ErasedContextFunctionN for N > 0
/** name[0..suffixStart) == `str` */
private def isPreceded(str: String, suffixStart: Int) =
str.length == suffixStart && name.firstPart.startsWith(str)

/** Same as `funArity`, except that it returns -1 if the prefix
* is not one of "", "Context", "Erased", "ErasedContext"
*/
private def checkedFunArity(suffixStart: Int) =
if suffixStart == 0
|| isPreceded("Context", suffixStart)
|| isPreceded("Erased", suffixStart)
|| isPreceded("ErasedContext", suffixStart)
then funArity(suffixStart)
else -1

/** Is a function name, i.e one of FunctionXXL, FunctionN, ContextFunctionN, ErasedFunctionN, ErasedContextFunctionN for N >= 0
*/
def isFunction: Boolean = (name eq tpnme.FunctionXXL) || functionArity >= 0
def isFunction: Boolean =
(name eq tpnme.FunctionXXL) || checkedFunArity(functionSuffixStart) >= 0

/** Is an context function name, i.e one of ContextFunctionN for N >= 0 or ErasedContextFunctionN for N > 0
/** Is an context function name, i.e one of ContextFunctionN or ErasedContextFunctionN for N >= 0
*/
def isContextFunction: Boolean =
functionArityFor(str.ContextFunction) >= 0 ||
functionArityFor(str.ErasedContextFunction) > 0
val suffixStart = functionSuffixStart
(isPreceded("Context", suffixStart) || isPreceded("ErasedContext", suffixStart))
&& funArity(suffixStart) >= 0

/** Is an erased function name, i.e. one of ErasedFunctionN, ErasedContextFunctionN for N > 0
/** Is an erased function name, i.e. one of ErasedFunctionN, ErasedContextFunctionN for N >= 0
*/
def isErasedFunction: Boolean =
functionArityFor(str.ErasedFunction) > 0 ||
functionArityFor(str.ErasedContextFunction) > 0
val suffixStart = functionSuffixStart
(isPreceded("Erased", suffixStart) || isPreceded("ErasedContext", suffixStart))
&& funArity(suffixStart) >= 0

/** Is a synthetic function name, i.e. one of
* - FunctionN for N > 22
* - ContextFunctionN for N >= 0
* - ErasedFunctionN for N > 0
* - ErasedContextFunctionN for N > 0
* - ErasedFunctionN for N >= 0
* - ErasedContextFunctionN for N >= 0
*/
def isSyntheticFunction: Boolean =
functionArityFor(str.Function) > MaxImplementedFunctionArity ||
functionArityFor(str.ContextFunction) >= 0 ||
isErasedFunction
val suffixStart = functionSuffixStart
if suffixStart == 0 then funArity(suffixStart) > MaxImplementedFunctionArity
else checkedFunArity(suffixStart) >= 0

/** Parsed function arity for function with some specific prefix */
private def functionArityFor(prefix: String): Int =
inline val MaxSafeInt = MaxValue / 10
val first = name.firstPart
def collectDigits(acc: Int, idx: Int): Int =
if idx == first.length then acc
else
val d = digit2int(first(idx), 10)
if d < 0 || acc > MaxSafeInt then -1
else collectDigits(acc * 10 + d, idx + 1)
if first.startsWith(prefix) && prefix.length < first.length then
collectDigits(0, prefix.length)
else
-1
def functionArity: Int =
val suffixStart = functionSuffixStart
if suffixStart >= 0 then checkedFunArity(suffixStart) else -1

/** The name of the generic runtime operation corresponding to an array operation */
def genericArrayOp: TermName = name match {
Expand Down
14 changes: 11 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1098,10 +1098,18 @@ object Types {
* def o: Outer
* <o.x.type>.widen = o.C
*/
final def widen(using Context): Type = widenSingleton match {
final def widen(using Context): Type = this match
case _: TypeRef | _: MethodOrPoly => this // fast path for most frequent cases
case tp: TermRef => // fast path for next most frequent case
if tp.isOverloaded then tp else tp.underlying.widen
case tp: SingletonType => tp.underlying.widen
case tp: ExprType => tp.resultType.widen
case tp => tp
}
case tp =>
val tp1 = tp.stripTypeVar.stripAnnots
if tp1 eq tp then tp
else
val tp2 = tp1.widen
if tp2 ne tp1 then tp2 else tp

/** Widen from singleton type to its underlying non-singleton
* base type by applying one or more `underlying` dereferences.
Expand Down
12 changes: 8 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1447,7 +1447,7 @@ trait Applications extends Compatibility {
* that takes fewer.
*/
def compare(alt1: TermRef, alt2: TermRef)(using Context): Int = trace(i"compare($alt1, $alt2)", overload) {
record("compare")
record("resolveOverloaded.compare")

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

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

Expand Down Expand Up @@ -1840,8 +1840,8 @@ trait Applications extends Compatibility {
alts.filterConserve(sizeFits(_))

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

record("resolveOverloaded.FunProto", alts.length)
val alts1 = narrowBySize(alts)
//report.log(i"narrowed by size: ${alts1.map(_.symbol.showDcl)}%, %")
if isDetermined(alts1) then alts1
else
record("resolveOverloaded.narrowedBySize", alts1.length)
val alts2 = narrowByShapes(alts1)
//report.log(i"narrowed by shape: ${alts2.map(_.symbol.showDcl)}%, %")
if isDetermined(alts2) then alts2
else
record("resolveOverloaded.narrowedByShape", alts2.length)
pretypeArgs(alts2, pt)
narrowByTrees(alts2, pt.typedArgs(normArg(alts2, _, _)), resultType)

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

record("resolveOverloaded.narrowedApplicable", candidates.length)
val found = narrowMostSpecific(candidates)
if (found.length <= 1) found
else
Expand Down