From f61860632ed6084d7ff4f7d4dcbe61e9504d770e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jan 2019 19:02:24 +0100 Subject: [PATCH 01/19] Add closure count instrumentation Add a miniphase, enabled by -Yinstrument-closures that counts closure allocation by invoking Stats.doRecord. Todo: Find a generally shippable way to use this. Right now it depends on the compiler-internal utility class Stats. --- compiler/src/dotty/tools/dotc/Compiler.scala | 1 + .../tools/dotc/config/ScalaSettings.scala | 2 ++ .../dotty/tools/dotc/core/Definitions.scala | 3 +++ .../src/dotty/tools/dotc/core/Phases.scala | 19 +++++++++++++++---- .../src/dotty/tools/dotc/util/Stats.scala | 2 +- 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 3b31ffe7b363..e9e205a66d42 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -97,6 +97,7 @@ class Compiler { List(new Constructors, // Collect initialization code in primary constructors // Note: constructors changes decls in transformTemplate, no InfoTransformers should be added after it new FunctionalInterfaces, // Rewrites closures to implement @specialized types of Functions. + new InstrumentClosures, // Count closure allocations under -Yinstrument-closures new GetClass) :: // Rewrites getClass calls on primitive types. List(new LinkScala2Impls, // Redirect calls to trait methods defined by Scala 2.x, so that they now go to their implementations new LambdaLift, // Lifts out nested functions to class scope, storing free variables in environments diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 2026488631ba..540d936dc7a7 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -149,6 +149,8 @@ class ScalaSettings extends Settings.SettingGroup { val YnoDecodeStacktraces: Setting[Boolean] = BooleanSetting("-Yno-decode-stacktraces", "Show raw StackOverflow stacktraces, instead of decoding them into triggering operations.") + val YinstrumentClosures: Setting[Boolean] = BooleanSetting("-Yinstrument-closures", "Add instrumentation code that counts closure creations.") + /** Dottydoc specific settings */ val siteRoot: Setting[String] = StringSetting( "-siteroot", diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e44b02107266..4230356dc590 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -719,6 +719,9 @@ class Definitions { lazy val ValueOfType: TypeRef = ctx.requiredClassRef("scala.ValueOf") def ValueOfClass(implicit ctx: Context): ClassSymbol = ValueOfType.symbol.asClass + lazy val StatsModule = ctx.requiredModule("dotty.tools.dotc.util.Stats") + def Stats_doRecord(implicit ctx: Context): TermSymbol = StatsModule.requiredMethod("doRecord") + lazy val XMLTopScopeModuleRef: TermRef = ctx.requiredModuleRef("scala.xml.TopScope") lazy val TupleTypeRef: TypeRef = ctx.requiredClassRef("scala.Tuple") diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 60fcbe2acdb8..8de12f23b8f8 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -79,16 +79,25 @@ object Phases { * whereas a combined TreeTransformer gets period equal to union of periods of it's TreeTransforms */ final def squashPhases(phasess: List[List[Phase]], - phasesToSkip: List[String], stopBeforePhases: List[String], stopAfterPhases: List[String], YCheckAfter: List[String]): List[Phase] = { + phasesToSkip: List[String], + stopBeforePhases: List[String], + stopAfterPhases: List[String], + YCheckAfter: List[String])(implicit ctx: Context): List[Phase] = { val squashedPhases = ListBuffer[Phase]() var prevPhases: Set[String] = Set.empty val YCheckAll = YCheckAfter.contains("all") var stop = false + + def isEnabled(p: Phase): Boolean = + !stop && + !stopBeforePhases.contains(p.phaseName) && + !phasesToSkip.contains(p.phaseName) && + p.isEnabled + val filteredPhases = phasess.map(_.filter { p => - val pstop = stop - stop = stop | stopBeforePhases.contains(p.phaseName) | stopAfterPhases.contains(p.phaseName) - !(pstop || stopBeforePhases.contains(p.phaseName) || phasesToSkip.contains(p.phaseName)) + try isEnabled(p) + finally stop |= stopBeforePhases.contains(p.phaseName) | stopAfterPhases.contains(p.phaseName) }) var i = 0 @@ -323,6 +332,8 @@ object Phases { /** Can this transform change the base types of a type? */ def changesBaseTypes: Boolean = changesParents + def isEnabled(implicit ctx: Context): Boolean = true + def exists: Boolean = true def initContext(ctx: FreshContext): Unit = () diff --git a/compiler/src/dotty/tools/dotc/util/Stats.scala b/compiler/src/dotty/tools/dotc/util/Stats.scala index 7fec4c75bbcc..5e4fb773dc8e 100644 --- a/compiler/src/dotty/tools/dotc/util/Stats.scala +++ b/compiler/src/dotty/tools/dotc/util/Stats.scala @@ -26,7 +26,7 @@ import collection.mutable def record(fn: => String, n: => Int = 1): Unit = if (enabled) doRecord(fn, n) - private def doRecord(fn: String, n: Int) = + def doRecord(fn: String, n: Int) = if (monitored) { val name = if (fn.startsWith("member-")) "member" else fn hits(name) += n From e8b950399f962cec4d0e9d7ee6896474c87e2846 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Jan 2019 18:03:15 +0100 Subject: [PATCH 02/19] Also allow to count allocations in instrumentation --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 +- .../tools/dotc/config/ScalaSettings.scala | 1 + .../dotc/transform/Instrumentation.scala | 60 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/Instrumentation.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index e9e205a66d42..f35aa98cca9b 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -97,7 +97,7 @@ class Compiler { List(new Constructors, // Collect initialization code in primary constructors // Note: constructors changes decls in transformTemplate, no InfoTransformers should be added after it new FunctionalInterfaces, // Rewrites closures to implement @specialized types of Functions. - new InstrumentClosures, // Count closure allocations under -Yinstrument-closures + new Instrumentation, // Count closure allocations under -Yinstrument-closures new GetClass) :: // Rewrites getClass calls on primitive types. List(new LinkScala2Impls, // Redirect calls to trait methods defined by Scala 2.x, so that they now go to their implementations new LambdaLift, // Lifts out nested functions to class scope, storing free variables in environments diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 540d936dc7a7..5e24485616bf 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -150,6 +150,7 @@ class ScalaSettings extends Settings.SettingGroup { val YnoDecodeStacktraces: Setting[Boolean] = BooleanSetting("-Yno-decode-stacktraces", "Show raw StackOverflow stacktraces, instead of decoding them into triggering operations.") val YinstrumentClosures: Setting[Boolean] = BooleanSetting("-Yinstrument-closures", "Add instrumentation code that counts closure creations.") + val YinstrumentAllocations: Setting[Boolean] = BooleanSetting("-Yinstrument-allocations", "Add instrumentation code that counts allocations.") /** Dottydoc specific settings */ val siteRoot: Setting[String] = StringSetting( diff --git a/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala b/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala new file mode 100644 index 000000000000..7301101cbd27 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala @@ -0,0 +1,60 @@ +package dotty.tools.dotc +package transform + +import core._ +import Contexts.Context +import Symbols._ +import Flags._ +import SymDenotations._ + +import Decorators._ +import ast.Trees._ +import MegaPhase._ +import StdNames.nme +import Names.TermName +import Constants.Constant + + +/** The phase is enabled if a -Yinstrument-... option is set. + * If enabled, it counts the number of closures or allocations for each source position. + * It does this by generating a call to dotty.tools.dotc.util.Stats.doRecord. + */ +class Instrumentation extends MiniPhase { thisPhase => + import ast.tpd._ + + override def phaseName: String = "instrumentation" + + override def isEnabled(implicit ctx: Context) = + ctx.settings.YinstrumentClosures.value || + ctx.settings.YinstrumentAllocations.value + + private var consName: TermName = _ + private var consEqName: TermName = _ + + override def prepareForUnit(tree: Tree)(implicit ctx: Context): Context = { + consName = "::".toTermName + consEqName = "+=".toTermName + ctx + } + + private def record(category: String, tree: Tree)(implicit ctx: Context): Tree = { + val key = Literal(Constant(s"$category${tree.sourcePos.show}")) + ref(defn.Stats_doRecord).appliedTo(key, Literal(Constant(1))) + } + + override def transformApply(tree: Apply)(implicit ctx: Context): Tree = tree.fun match { + case Select(nu: New, _) => + cpy.Block(tree)(record(i"alloc/${nu.tpe}@", tree) :: Nil, tree) + case Select(_, name) if name == consName || name == consEqName => + cpy.Block(tree)(record("alloc/::", tree) :: Nil, tree) + case _ => + tree + } + + override def transformBlock(tree: Block)(implicit ctx: Context): Block = tree.expr match { + case _: Closure => + cpy.Block(tree)(record("closure/", tree) :: tree.stats, tree.expr) + case _ => + tree + } +} From e67ac0373bd127fda60ef96f0864079285c74256 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jan 2019 19:36:40 +0100 Subject: [PATCH 03/19] Eliminate a hot closure in Reporter --- .../src/dotty/tools/dotc/reporting/Reporter.scala | 13 +++++++++---- compiler/src/dotty/tools/repl/ParseResult.scala | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index ad4a073e46fb..48ffa2ead61d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -31,8 +31,12 @@ object Reporter { def doReport(m: MessageContainer)(implicit ctx: Context): Unit = () override def report(m: MessageContainer)(implicit ctx: Context): Unit = () } -} + type ErrorHandler = (MessageContainer, Context) => Unit + + private val defaultIncompleteHandler: ErrorHandler = + (mc, ctx) => ctx.reporter.report(mc)(ctx) +} trait Reporting { this: Context => @@ -138,6 +142,7 @@ trait Reporting { this: Context => * error messages. */ abstract class Reporter extends interfaces.ReporterResult { + import Reporter._ /** Report a diagnostic */ def doReport(m: MessageContainer)(implicit ctx: Context): Unit @@ -155,8 +160,8 @@ abstract class Reporter extends interfaces.ReporterResult { finally _truncationOK = saved } - type ErrorHandler = MessageContainer => Context => Unit - private[this] var incompleteHandler: ErrorHandler = d => c => report(d)(c) + private[this] var incompleteHandler: ErrorHandler = defaultIncompleteHandler + def withIncompleteHandler[T](handler: ErrorHandler)(op: => T): T = { val saved = incompleteHandler incompleteHandler = handler @@ -204,7 +209,7 @@ abstract class Reporter extends interfaces.ReporterResult { } def incomplete(m: MessageContainer)(implicit ctx: Context): Unit = - incompleteHandler(m)(ctx) + incompleteHandler(m, ctx) /** Summary of warnings and errors */ def summary: String = { diff --git a/compiler/src/dotty/tools/repl/ParseResult.scala b/compiler/src/dotty/tools/repl/ParseResult.scala index 8bfb3864dff7..d949f1f997d2 100644 --- a/compiler/src/dotty/tools/repl/ParseResult.scala +++ b/compiler/src/dotty/tools/repl/ParseResult.scala @@ -157,7 +157,7 @@ object ParseResult { case _ => { val reporter = newStoreReporter var needsMore = false - reporter.withIncompleteHandler(_ => _ => needsMore = true) { + reporter.withIncompleteHandler((_, _) => needsMore = true) { parseStats(sourceCode)(ctx.fresh.setReporter(reporter)) !reporter.hasErrors && needsMore } From 85a9257015a80ae8e257206773e5dfbeae338b05 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jan 2019 19:37:25 +0100 Subject: [PATCH 04/19] Eliminate a hot closure in ConstFold by inlining --- compiler/src/dotty/tools/dotc/typer/ConstFold.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala index 0fabadf4bb3e..43567923b1c2 100644 --- a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala +++ b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala @@ -48,7 +48,7 @@ object ConstFold { } } - private def finish[T <: Tree](tree: T)(compX: => Constant)(implicit ctx: Context): T = + @forceInline private def finish[T <: Tree](tree: T)(compX: => Constant)(implicit ctx: Context): T = try { val x = compX if (x ne null) tree.withType(ConstantType(x)).asInstanceOf[T] From 218a673bf6421ef7b5a1d70dc7182ba1ff809958 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jan 2019 20:26:26 +0100 Subject: [PATCH 05/19] Optimization: Make Reporter#unreportedWarnings an immutable map --- compiler/src/dotty/tools/dotc/reporting/Reporter.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 48ffa2ead61d..7264cd6f70f9 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -190,15 +190,16 @@ abstract class Reporter extends interfaces.ReporterResult { def reportNewFeatureUseSite(featureTrait: Symbol): Unit = reportedFeaturesUseSites += featureTrait - val unreportedWarnings: mutable.HashMap[String, Int] = new mutable.HashMap[String, Int] { - override def default(key: String) = 0 - } + var unreportedWarnings: Map[String, Int] = Map.empty def report(m: MessageContainer)(implicit ctx: Context): Unit = if (!isHidden(m)) { doReport(m)(ctx.addMode(Mode.Printing)) m match { - case m: ConditionalWarning if !m.enablingOption.value => unreportedWarnings(m.enablingOption.name) += 1 + case m: ConditionalWarning if !m.enablingOption.value => + val key = m.enablingOption.name + unreportedWarnings = + unreportedWarnings.updated(key, unreportedWarnings.getOrElse(key, 0) + 1) case m: Warning => _warningCount += 1 case m: Error => errors = m :: errors From d820034a387aa7d00664ceab82c12910e10e500a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jan 2019 20:47:30 +0100 Subject: [PATCH 06/19] Optimization: Eliminate closures in PositionPickler#traverse --- .../tools/dotc/core/tasty/PositionPickler.scala | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala index b563f5408ac8..d7587c41fcc9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala @@ -88,13 +88,18 @@ class PositionPickler(pickler: TastyPickler, addrOfTree: untpd.Tree => Option[Ad } } x match { - case x: untpd.MemberDef @unchecked => - for (ann <- x.symbol.annotations) traverse(ann.tree, x.source) + case x: untpd.MemberDef @unchecked => traverse(x.symbol.annotations, x.source) case _ => } - traverse(x.productIterator, x.source) - case xs: TraversableOnce[_] => - xs.foreach(traverse(_, current)) + val limit = x.productArity + var n = 0 + while (n < limit) { + traverse(x.productElement(n), x.source) + n += 1 + } + case y :: ys => + traverse(y, current) + traverse(ys, current) case x: Annotation => traverse(x.tree, current) case _ => From 73c0d3c714f6403a477431d6bef8814217a36900 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jan 2019 20:55:23 +0100 Subject: [PATCH 07/19] Optimization: Eliminate a hot closure in Implicits --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 44dcfdd15f3e..3354114366c0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -92,23 +92,26 @@ object Implicits { def viewCandidateKind(tpw: Type, argType: Type, resType: Type): Candidate.Kind = { - def methodCandidateKind(mt: MethodType, formal: => Type) = + def methodCandidateKind(mt: MethodType, approx: Boolean) = if (!mt.isImplicitMethod && - mt.paramInfos.lengthCompare(1) == 0 && - ctx.test(implicit ctx => argType relaxed_<:< formal)) + mt.paramInfos.lengthCompare(1) == 0 && { + var formal = widenSingleton(mt.paramInfos.head) + if (approx) formal = wildApprox(formal) + ctx.test(implicit ctx => argType relaxed_<:< formal) + }) Candidate.Conversion else Candidate.None tpw match { case mt: MethodType => - methodCandidateKind(mt, widenSingleton(mt.paramInfos.head)) + methodCandidateKind(mt, approx = false) case poly: PolyType => // We do not need to call ProtoTypes#constrained on `poly` because // `candidateKind` is always called with mode TypevarsMissContext enabled. poly.resultType match { case mt: MethodType => - methodCandidateKind(mt, wildApprox(widenSingleton(mt.paramInfos.head))) + methodCandidateKind(mt, approx = true) case rtp => viewCandidateKind(wildApprox(rtp), argType, resType) } From ae4076188778ba72b107ed8808593f69f070701c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jan 2019 22:01:34 +0100 Subject: [PATCH 08/19] Optimization: Don't result of addrOfTree in Some Instead use NoAddr as a sentinel for None. --- .../dotc/core/tasty/CommentPickler.scala | 8 +++--- .../dotc/core/tasty/PositionPickler.scala | 25 +++++++++---------- .../tools/dotc/core/tasty/TastyPickler.scala | 2 +- .../tools/dotc/core/tasty/TreeBuffer.scala | 8 +++--- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala index 426f0194ccad..f6d7fb097bee 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala @@ -3,11 +3,11 @@ package dotty.tools.dotc.core.tasty import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Comments.{Comment, CommentsContext, ContextDocstrings} import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.dotc.core.tasty.TastyBuffer.Addr +import dotty.tools.dotc.core.tasty.TastyBuffer.{Addr, NoAddr} import java.nio.charset.Charset -class CommentPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr])(implicit ctx: Context) { +class CommentPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Addr)(implicit ctx: Context) { private[this] val buf = new TastyBuffer(5000) pickler.newSection("Comments", buf) @@ -16,8 +16,8 @@ class CommentPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr] new Traverser(ctx.docCtx.get).traverse(root) } - def pickleComment(addrOfTree: Option[Addr], comment: Option[Comment]): Unit = (addrOfTree, comment) match { - case (Some(addr), Some(cmt)) => + def pickleComment(addr: Addr, comment: Option[Comment]): Unit = comment match { + case Some(cmt) if addr != NoAddr => val bytes = cmt.raw.getBytes(Charset.forName("UTF-8")) val length = bytes.length buf.writeAddr(addr) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala index d7587c41fcc9..bfaf60ef2ad2 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala @@ -14,7 +14,7 @@ import TastyBuffer._ import util.Spans._ import TastyFormat.SOURCE -class PositionPickler(pickler: TastyPickler, addrOfTree: untpd.Tree => Option[Addr]) { +class PositionPickler(pickler: TastyPickler, addrOfTree: untpd.Tree => Addr) { val buf: TastyBuffer = new TastyBuffer(5000) pickler.newSection("Positions", buf) import ast.tpd._ @@ -72,19 +72,18 @@ class PositionPickler(pickler: TastyPickler, addrOfTree: untpd.Tree => Option[Ad def traverse(x: Any, current: SourceFile): Unit = x match { case x: untpd.Tree => if (x.span.exists) { - val sourceChange = x.source != current - val needsPos = x.span.toSynthetic != x.envelope(x.source) || alwaysNeedsPos(x) - addrOfTree(x) match { - case Some(addr) - if needsPos && !pickledIndices.contains(addr.index) || sourceChange => - // we currently do not share trees when unpickling, so if one path to a tree contains - // a source change while another does not, we have to record the position of the tree twice - // in order not to miss the source change. Test case is t3232a.scala. - //println(i"pickling $x with $span at $addr") + val addr = addrOfTree(x) + if (addr != NoAddr) { + if (x.source != current) { + // we currently do not share trees when unpickling, so if one path to a tree contains + // a source change while another does not, we have to record the position of the tree twice + // in order not to miss the source change. Test case is t3232a.scala. + pickleDeltas(addr.index, x.span) + pickleSource(x.source) + } + else if (!pickledIndices.contains(addr.index) && + (x.span.toSynthetic != x.envelope(x.source) || alwaysNeedsPos(x))) pickleDeltas(addr.index, x.span) - if (sourceChange) pickleSource(x.source) - case _ => - //println(i"no address for $x") } } x match { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala index b51af96b9720..97d6d0077bb9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala @@ -67,7 +67,7 @@ class TastyPickler(val rootCls: ClassSymbol) { * Note that trees are looked up by reference equality, * so one can reliably use this function only directly after `pickler`. */ - var addrOfTree: tpd.Tree => Option[Addr] = (_ => None) + var addrOfTree: tpd.Tree => Addr = (_ => NoAddr) /** * Addresses in TASTY file of symbols, stored by pickling. diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeBuffer.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeBuffer.scala index 2de9a776b53b..12ddbe163051 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeBuffer.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeBuffer.scala @@ -4,7 +4,7 @@ package core package tasty import util.Util.{bestFit, dble} -import TastyBuffer.{Addr, AddrWidth} +import TastyBuffer.{Addr, NoAddr, AddrWidth} import config.Printers.pickling import ast.untpd.Tree @@ -25,9 +25,9 @@ class TreeBuffer extends TastyBuffer(50000) { case addr: Addr => addr } - def addrOfTree(tree: Tree): Option[Addr] = treeAddrs.get(tree) match { - case null => None - case addr: Addr => Some(addr) + def addrOfTree(tree: Tree): Addr = treeAddrs.get(tree) match { + case null => NoAddr + case addr: Addr => addr } private def offset(i: Int): Addr = Addr(offsets(i)) From 9d436eafe96ed35b2e68a0ce6d7c65f6281ed066 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jan 2019 22:06:54 +0100 Subject: [PATCH 09/19] Optimization: Specialize tpes --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 4bec4ee635d8..da8ae2f1576b 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -978,7 +978,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } implicit class ListOfTreeDecorator(val xs: List[tpd.Tree]) extends AnyVal { - def tpes: List[Type] = xs map (_.tpe) + def tpes: List[Type] = xs match { + case x :: xs1 => x.tpe :: xs1.tpes + case nil => Nil + } } /** A trait for loaders that compute trees. Currently implemented just by DottyUnpickler. */ From ee5d6809a46e0d8a14e9bda11ad5cc272c2c2085 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jan 2019 22:56:53 +0100 Subject: [PATCH 10/19] Optimize containsPhase Avpid creation of moeratly hot closure --- .../dotty/tools/dotc/core/Decorators.scala | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 8d8bdffa7151..ccf02262a4df 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -151,18 +151,21 @@ object Decorators { * exact meaning of "contains" here. */ implicit class PhaseListDecorator(val names: List[String]) extends AnyVal { - def containsPhase(phase: Phase): Boolean = phase match { - case phase: MegaPhase => phase.miniPhases.exists(containsPhase) - case _ => - names exists { name => - name == "all" || { - val strippedName = name.stripSuffix("+") - val logNextPhase = name != strippedName - phase.phaseName.startsWith(strippedName) || - (logNextPhase && phase.prev.phaseName.startsWith(strippedName)) - } + def containsPhase(phase: Phase): Boolean = + names.nonEmpty && { + phase match { + case phase: MegaPhase => phase.miniPhases.exists(containsPhase) + case _ => + names exists { name => + name == "all" || { + val strippedName = name.stripSuffix("+") + val logNextPhase = name != strippedName + phase.phaseName.startsWith(strippedName) || + (logNextPhase && phase.prev.phaseName.startsWith(strippedName)) + } + } } - } + } } implicit class genericDeco[T](val x: T) extends AnyVal { From f6b4559f6adc831170d3be79c78c92484f7ef350 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Jan 2019 08:51:14 +0100 Subject: [PATCH 11/19] Optimization: Avoid a closure in computeBaseData --- .../dotty/tools/dotc/core/SymDenotations.scala | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index b1e237f6d62c..d25f05f234de 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1530,11 +1530,16 @@ object SymDenotations { if (classParents.isEmpty && !emptyParentsExpected) onBehalf.signalProvisional() val builder = new BaseDataBuilder - for (p <- classParents) - p.classSymbol match { - case pcls: ClassSymbol => builder.addAll(pcls.baseClasses) - case _ => assert(isRefinementClass || ctx.mode.is(Mode.Interactive), s"$this has non-class parent: $p") - } + def traverse(parents: List[Type]): Unit = parents match { + case p :: parents1 => + p.classSymbol match { + case pcls: ClassSymbol => builder.addAll(pcls.baseClasses) + case _ => assert(isRefinementClass || ctx.mode.is(Mode.Interactive), s"$this has non-class parent: $p") + } + traverse(parents1) + case nil => + } + traverse(classParents) (classSymbol :: builder.baseClasses, builder.baseClassSet) } From e46529fb1a8908277284d8a478b95a245a9f098b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Jan 2019 18:02:25 +0100 Subject: [PATCH 12/19] Optimization: Avoid hot lazy vals in adaptNoArgs --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 0b726256c0b3..cbd70903c6f9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2567,17 +2567,19 @@ class Typer extends Namer def adaptNoArgs(wtp: Type): Tree = { val ptNorm = underlyingApplied(pt) - lazy val functionExpected = defn.isFunctionType(ptNorm) - lazy val resultMatch = constrainResult(tree.symbol, wtp, followAlias(pt)) + def functionExpected = defn.isFunctionType(ptNorm) + def resultMatch = constrainResult(tree.symbol, wtp, followAlias(pt)) def needsEta = pt match { case _: SingletonType => false case IgnoredProto(_: FunOrPolyProto) => false case _ => true } + var resMatch: Boolean = false wtp match { case wtp: ExprType => readaptSimplified(tree.withType(wtp.resultType)) - case wtp: MethodType if wtp.isImplicitMethod && (resultMatch || !functionExpected) => + case wtp: MethodType if wtp.isImplicitMethod && + ({ resMatch = resultMatch; resMatch } || !functionExpected) => if (resultMatch || ctx.mode.is(Mode.ImplicitsEnabled)) adaptNoArgsImplicitMethod(wtp) else { // Don't proceed with implicit search if result type cannot match - the search @@ -2586,8 +2588,9 @@ class Typer extends Namer err.typeMismatch(tree, pt) } case wtp: MethodType if needsEta => + val funExpected = functionExpected val arity = - if (functionExpected) + if (funExpected) if (!isFullyDefined(pt, ForceDegree.none) && isFullyDefined(wtp, ForceDegree.none)) // if method type is fully defined, but expected type is not, // prioritize method parameter types as parameter types of the eta-expanded closure @@ -2598,7 +2601,7 @@ class Typer extends Namer if (nparams > 0 || pt.eq(AnyFunctionProto)) nparams else -1 // no eta expansion in this case } - adaptNoArgsUnappliedMethod(wtp, functionExpected, arity) + adaptNoArgsUnappliedMethod(wtp, funExpected, arity) case _ => adaptNoArgsOther(wtp) } From be65f09be7be6039f8554c2e52c15c1cdd706e0f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Jan 2019 18:12:03 +0100 Subject: [PATCH 13/19] Optimization: reduce creations of FlagConjunction --- .../src/dotty/tools/backend/jvm/DottyBackendInterface.scala | 2 +- compiler/src/dotty/tools/dotc/core/Flags.scala | 2 ++ compiler/src/dotty/tools/dotc/typer/Applications.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 6fe8caa9af2b..4e32634ae274 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -477,7 +477,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma // unrelated change. ctx.base.settings.YnoGenericSig.value || sym.is(Flags.Artifact) - || sym.is(Flags.allOf(Flags.Method, Flags.Lifted)) + || sym.is(Flags.LiftedMethod) || sym.is(Flags.Bridge) ) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 26a71b41747b..a442e505b701 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -698,6 +698,8 @@ object Flags { /** Labeled protected[this] */ final val ProtectedLocal: FlagConjunction = allOf(Protected, Local) + final val LiftedMethod: FlagConjunction = allOf(Lifted, Method) + /** Java symbol which is `protected` and `static` */ final val StaticProtected: FlagConjunction = allOf(JavaDefined, Protected, JavaStatic) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 8969a0088739..ec6ae4ff8d50 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1159,7 +1159,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * result matching `resultType`? */ def hasExtensionMethod(tp: Type, name: TermName, argType: Type, resultType: Type)(implicit ctx: Context) = { - val mbr = tp.memberBasedOnFlags(name, required = allOf(ExtensionMethod)) + val mbr = tp.memberBasedOnFlags(name, required = ExtensionMethod) mbr.exists && isApplicable(tp.select(name, mbr), argType :: Nil, resultType) } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 3354114366c0..5ad489f41340 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -143,7 +143,7 @@ object Implicits { tpw.derivesFrom(defn.Predef_Conforms) && ref.symbol != defn.Predef_conforms val hasExtensions = resType match { case SelectionProto(name, _, _, _) => - tpw.memberBasedOnFlags(name, required = allOf(ExtensionMethod)).exists + tpw.memberBasedOnFlags(name, required = ExtensionMethod).exists case _ => false } val conversionKind = From 7034d91fafe07556da7404ac6cb717899386ffa6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Jan 2019 18:15:27 +0100 Subject: [PATCH 14/19] Statistics reporting: aggregate keys with common prefix --- compiler/src/dotty/tools/dotc/util/Stats.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/util/Stats.scala b/compiler/src/dotty/tools/dotc/util/Stats.scala index 5e4fb773dc8e..746b5e554e8d 100644 --- a/compiler/src/dotty/tools/dotc/util/Stats.scala +++ b/compiler/src/dotty/tools/dotc/util/Stats.scala @@ -75,6 +75,17 @@ import collection.mutable } } + final val GroupChar = '/' + + /** Aggregate all counts of all keys with a common prefix, followed by `:` */ + private def aggregate(): Unit = { + val groups = hits.keys + .filter(_.contains(GroupChar)) + .groupBy(_.takeWhile(_ != GroupChar)) + for ((prefix, names) <- groups; name <- names) + hits(s"Total $prefix") += hits(name) + } + def maybeMonitored[T](op: => T)(implicit ctx: Context): T = { if (ctx.settings.YdetailedStats.value) { val hb = new HeartBeat() @@ -83,6 +94,7 @@ import collection.mutable try op finally { hb.continue = false + aggregate() println() println(hits.toList.sortBy(_._2).map{ case (x, y) => s"$x -> $y" } mkString "\n") println(s"uniqueInfo (size, accesses, collisions): ${ctx.base.uniquesSizes}") From 8175d6a56bad96efc56423201844bbcabfdaf5df Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Jan 2019 18:17:19 +0100 Subject: [PATCH 15/19] Optimization: Specialize ==, != on value classes Without the optimization a comparison of value classes always boxing, since the rhs operand is cast to Any. Most likely these are eliminated by escape analysis, but by specializing here we make the job of the JOT compiler easier. --- compiler/src/dotty/tools/dotc/core/Mode.scala | 3 +++ compiler/src/dotty/tools/dotc/core/Periods.scala | 3 +++ compiler/src/dotty/tools/dotc/core/tasty/TastyBuffer.scala | 3 +++ compiler/src/dotty/tools/dotc/util/Spans.scala | 3 +++ 4 files changed, 12 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 311428e905b2..bef2a0e50c2b 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -12,6 +12,9 @@ case class Mode(val bits: Int) extends AnyVal { override def toString: String = (0 until 31).filter(i => (bits & (1 << i)) != 0).map(modeName).mkString("Mode(", ",", ")") + + def ==(that: Mode): Boolean = this.bits == that.bits + def !=(that: Mode): Boolean = this.bits != that.bits } object Mode { diff --git a/compiler/src/dotty/tools/dotc/core/Periods.scala b/compiler/src/dotty/tools/dotc/core/Periods.scala index be704dfcda8e..f1bfa4d0c7f6 100644 --- a/compiler/src/dotty/tools/dotc/core/Periods.scala +++ b/compiler/src/dotty/tools/dotc/core/Periods.scala @@ -123,6 +123,9 @@ object Periods { this.lastPhaseId max that.lastPhaseId) override def toString: String = s"Period($firstPhaseId..$lastPhaseId, run = $runId)" + + def ==(that: Period): Boolean = this.code == that.code + def !=(that: Period): Boolean = this.code != that.code } object Period { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyBuffer.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyBuffer.scala index a75d1750dcbe..4e61f615aceb 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyBuffer.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyBuffer.scala @@ -20,6 +20,9 @@ object TastyBuffer { def + (delta: Int): Addr = Addr(this.index + delta) def relativeTo(base: Addr): Addr = this - base.index - AddrWidth + + def ==(that: Addr): Boolean = this.index == that.index + def !=(that: Addr): Boolean = this.index != that.index } val NoAddr: Addr = Addr(-1) diff --git a/compiler/src/dotty/tools/dotc/util/Spans.scala b/compiler/src/dotty/tools/dotc/util/Spans.scala index 1672d3468fa1..c3b1e371e7a2 100644 --- a/compiler/src/dotty/tools/dotc/util/Spans.scala +++ b/compiler/src/dotty/tools/dotc/util/Spans.scala @@ -127,6 +127,9 @@ object Spans { else s"${left}no position${right}" } + + def ==(that: Span): Boolean = this.coords == that.coords + def !=(that: Span): Boolean = this.coords != that.coords } private def fromOffsets(start: Int, end: Int, pointDelta: Int) = { From 7dceba36ba1b66db46c69a1123e412c17444fd2e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Jan 2019 18:19:37 +0100 Subject: [PATCH 16/19] Eliminate some hot allocations Again, these might be eliminated by escape analysis, but it does not hurt to not allocate in the first place. --- compiler/src/dotty/tools/dotc/ast/Positioned.scala | 6 +++++- compiler/src/dotty/tools/dotc/core/Names.scala | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index f764366302da..7b789c017b73 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -51,8 +51,12 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Pro else { val newpd: this.type = if (mySpan.isSynthetic) { - if (!mySpan.exists && span.exists) + if (!mySpan.exists && span.exists) { envelope(source, span.startPos) // fill in children spans + () // Note: the `()` is there to prevent some inefficient code from being generated. + // Without it we get an allocation of a span here since the result type of the `if` + // is `Any`, the lub of `Span` and `Unit`. + } this } else cloneIn(source) diff --git a/compiler/src/dotty/tools/dotc/core/Names.scala b/compiler/src/dotty/tools/dotc/core/Names.scala index aa909c68429e..26c9d9d5e777 100644 --- a/compiler/src/dotty/tools/dotc/core/Names.scala +++ b/compiler/src/dotty/tools/dotc/core/Names.scala @@ -570,7 +570,7 @@ object Names { */ def termName(cs: Array[Char], offset: Int, len: Int): SimpleName = synchronized { util.Stats.record("termName") - val h = hashValue(cs, offset, len) & (table.size - 1) + val h = hashValue(cs, offset, len) & (table.length - 1) /** Make sure the capacity of the character array is at least `n` */ def ensureCapacity(n: Int) = From b81b3b9fd50986cd6486a2c7d870c0759d3fff27 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 18 Jan 2019 15:28:36 +0100 Subject: [PATCH 17/19] Record stats of SimpleIdentityMap sizes >= 4 --- compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala b/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala index ad0ec87067c5..ce91764fb4ca 100644 --- a/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala +++ b/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala @@ -139,6 +139,7 @@ object SimpleIdentityMap { private def value(i: Int): V = bindings(i + 1).asInstanceOf[V] def size: Int = bindings.length / 2 + Stats.record(s"SimpleIdentityMap/$size") def apply(k: K): V = { var i = 0 From b3698d02a26d2e8f6514912fe62ec705fa198bb6 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sat, 19 Jan 2019 12:38:26 +0100 Subject: [PATCH 18/19] Update compiler/src/dotty/tools/dotc/typer/Typer.scala Co-Authored-By: odersky --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index cbd70903c6f9..bc3805de733d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2580,7 +2580,7 @@ class Typer extends Namer readaptSimplified(tree.withType(wtp.resultType)) case wtp: MethodType if wtp.isImplicitMethod && ({ resMatch = resultMatch; resMatch } || !functionExpected) => - if (resultMatch || ctx.mode.is(Mode.ImplicitsEnabled)) adaptNoArgsImplicitMethod(wtp) + if (resMatch || ctx.mode.is(Mode.ImplicitsEnabled)) adaptNoArgsImplicitMethod(wtp) else { // Don't proceed with implicit search if result type cannot match - the search // will likely be under-constrained, which means that an unbounded number of alternatives From 2ee9994efaf6b3b8b97377002c4a237fe9b5ce00 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sat, 19 Jan 2019 17:44:42 +0100 Subject: [PATCH 19/19] Update compiler/src/dotty/tools/dotc/typer/Typer.scala Co-Authored-By: odersky --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bc3805de733d..85cc5ccad59c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2580,7 +2580,7 @@ class Typer extends Namer readaptSimplified(tree.withType(wtp.resultType)) case wtp: MethodType if wtp.isImplicitMethod && ({ resMatch = resultMatch; resMatch } || !functionExpected) => - if (resMatch || ctx.mode.is(Mode.ImplicitsEnabled)) adaptNoArgsImplicitMethod(wtp) + if (resMatch || ctx.mode.is(Mode.ImplicitsEnabled)) adaptNoArgsImplicitMethod(wtp) else { // Don't proceed with implicit search if result type cannot match - the search // will likely be under-constrained, which means that an unbounded number of alternatives