From 44b98ce65632e4cb41ca8885f0278c8a91342946 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Sep 2020 14:47:01 +0200 Subject: [PATCH 1/4] Better caching of prototypes - Make IgnoredProto a cached type - Convert IgnroedProtos to WildcardTypes in wildApprox - Use `eql` instead of `equals` when hash-consing prototypes --- .../dotty/tools/dotc/typer/ProtoTypes.scala | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index fa368a0b7213..3c3a0e2817c8 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -120,14 +120,22 @@ object ProtoTypes { } /** A class marking ignored prototypes that can be revealed by `deepenProto` */ - case class IgnoredProto(ignored: Type) extends UncachedGroundType with MatchAlways: + abstract case class IgnoredProto(ignored: Type) extends CachedGroundType with MatchAlways: override def revealIgnored = ignored override def deepenProto(using Context): Type = ignored + override def computeHash(bs: Hashable.Binders): Int = doHash(bs, ignored) + + override def eql(that: Type): Boolean = that match + case that: IgnoredProto => ignored eq that.ignored + case _ => false + + final class CachedIgnoredProto(ignored: Type) extends IgnoredProto(ignored) + object IgnoredProto: def apply(ignored: Type): IgnoredProto = ignored match case ignored: IgnoredProto => ignored - case _ => new IgnoredProto(ignored) + case _ => CachedIgnoredProto(ignored) /** A prototype for expressions [] that are part of a selection operation: * @@ -185,13 +193,6 @@ object ProtoTypes { if ((name eq this.name) && (memberProto eq this.memberProto) && (compat eq this.compat)) this else SelectionProto(name, memberProto, compat, privateOK) - override def equals(that: Any): Boolean = that match { - case that: SelectionProto => - (name eq that.name) && (memberProto == that.memberProto) && (compat eq that.compat) && (privateOK == that.privateOK) - case _ => - false - } - def map(tm: TypeMap)(using Context): SelectionProto = derivedSelectionProto(name, tm(memberProto), compat) def fold[T](x: T, ta: TypeAccumulator[T])(using Context): T = ta(x, memberProto) @@ -201,6 +202,13 @@ object ProtoTypes { val delta = (if (compat eq NoViewsAllowed) 1 else 0) | (if (privateOK) 2 else 0) addDelta(doHash(bs, name, memberProto), delta) } + + override def eql(that: Type): Boolean = that match { + case that: SelectionProto => + (name eq that.name) && (memberProto eq that.memberProto) && (compat eq that.compat) && (privateOK == that.privateOK) + case _ => + false + } } class CachedSelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean) @@ -450,6 +458,9 @@ object ProtoTypes { class CachedViewProto(argType: Type, resultType: Type) extends ViewProto(argType, resultType) { override def computeHash(bs: Hashable.Binders): Int = doHash(bs, argType, resultType) + override def eql(that: Type): Boolean = that match + case that: ViewProto => (argType eq that.argType) && (resType eq that.resType) + case _ => false } object ViewProto { @@ -680,6 +691,8 @@ object ProtoTypes { tp.derivedViewProto( wildApprox(tp.argType, theMap, seen, internal), wildApprox(tp.resultType, theMap, seen, internal)) + case tp: IgnoredProto => + WildcardType case _: ThisType | _: BoundType => // default case, inlined for speed tp case tl: TypeLambda => From 56e540427121bc529c28028aded935f46cefb194 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Sep 2020 09:30:37 +0200 Subject: [PATCH 2/4] Cache IgnoredProtos --- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 3c3a0e2817c8..cfb8ccc9ca07 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -133,9 +133,9 @@ object ProtoTypes { final class CachedIgnoredProto(ignored: Type) extends IgnoredProto(ignored) object IgnoredProto: - def apply(ignored: Type): IgnoredProto = ignored match + def apply(ignored: Type)(using Context): IgnoredProto = ignored match case ignored: IgnoredProto => ignored - case _ => CachedIgnoredProto(ignored) + case _ => unique(CachedIgnoredProto(ignored)) /** A prototype for expressions [] that are part of a selection operation: * From c997ed10376492d32fe6e5d6ee90fe5bbe56c51b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Sep 2020 14:08:00 +0200 Subject: [PATCH 3/4] Put back `equals` in SelectionProto --- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index cfb8ccc9ca07..08485bf9799e 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -130,6 +130,9 @@ object ProtoTypes { case that: IgnoredProto => ignored eq that.ignored case _ => false + // equals comes from case class; no need to redefine + end IgnoredProto + final class CachedIgnoredProto(ignored: Type) extends IgnoredProto(ignored) object IgnoredProto: @@ -197,12 +200,17 @@ object ProtoTypes { def fold[T](x: T, ta: TypeAccumulator[T])(using Context): T = ta(x, memberProto) override def deepenProto(using Context): SelectionProto = derivedSelectionProto(name, memberProto.deepenProto, compat) - override def computeHash(bs: Hashable.Binders): Int = { val delta = (if (compat eq NoViewsAllowed) 1 else 0) | (if (privateOK) 2 else 0) addDelta(doHash(bs, name, memberProto), delta) } + override def equals(that: Any): Boolean = that match + case that: SelectionProto => + (name eq that.name) && memberProto.equals(that.memberProto) && (compat eq that.compat) && (privateOK == that.privateOK) + case _ => + false + override def eql(that: Type): Boolean = that match { case that: SelectionProto => (name eq that.name) && (memberProto eq that.memberProto) && (compat eq that.compat) && (privateOK == that.privateOK) @@ -461,6 +469,7 @@ object ProtoTypes { override def eql(that: Type): Boolean = that match case that: ViewProto => (argType eq that.argType) && (resType eq that.resType) case _ => false + // equals comes from case class; no need to redefine } object ViewProto { From b0ce52993234f6fde812046264a742253095030a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Sep 2020 11:53:32 +0200 Subject: [PATCH 4/4] Revert "Allow forcing traces" This reverts commit 83e6ad4bc2d8a74d1b661f8b700aa5aa94c3085e. Also, optimize trace further to be zero overhead # Conflicts: # compiler/src/dotty/tools/dotc/reporting/trace.scala --- .../dotty/tools/dotc/core/SymbolLoaders.scala | 7 +- .../dotty/tools/dotc/reporting/trace.scala | 102 ++++++------------ 2 files changed, 37 insertions(+), 72 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index 6416c38d007a..178477da829d 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -342,12 +342,9 @@ abstract class SymbolLoader extends LazyType { self => } try { val start = System.currentTimeMillis - if (Config.tracingEnabled && ctx.settings.YdebugTrace.value) - trace(s">>>> loading ${root.debugString}", _ => s"<<<< loaded ${root.debugString}") { - doComplete(root) - } - else + trace.onDebug("loading") { doComplete(root) + } report.informTime("loaded " + description, start) } catch { diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala index bbecb7238328..583c650258e6 100644 --- a/compiler/src/dotty/tools/dotc/reporting/trace.scala +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -7,46 +7,28 @@ import config.Config import config.Printers import core.Mode -/** Exposes the {{{ trace("question") { op } }}} syntax. - * - * Traced operations will print indented messages if enabled. - * Tracing depends on [[Config.tracingEnabled]] and [[dotty.tools.dotc.config.ScalaSettings.Ylog]]. - * Tracing can be forced by replacing [[trace]] with [[trace.force]] (see below). - */ -object trace extends TraceSyntax { - final val isForced = false - - /** Forces a particular trace to be printed out regardless of tracing being enabled. */ - object force extends TraceSyntax { - final val isForced = true - } -} - -abstract class TraceSyntax { - val isForced: Boolean +/** This module is carefully optimized to give zero overhead if Config.tracingEnabled + * is false. The `trace` operation is called in various hotspots, so every tiny bit + * of overhead is unacceptable: boxing, closures, additional method calls are all out. + */ +object trace: inline def onDebug[TD](inline question: String)(inline op: TD)(using Context): TD = conditionally(ctx.settings.YdebugTrace.value, question, false)(op) - inline def conditionally[TC](inline cond: Boolean, inline question: String, inline show: Boolean)(op: => TC)(using Context): TC = - inline if (isForced || Config.tracingEnabled) { - if (cond) apply[TC](question, Printers.default, show)(op) - else op - } + inline def conditionally[TC](inline cond: Boolean, inline question: String, inline show: Boolean)(inline op: TC)(using Context): TC = + if Config.tracingEnabled then + apply(question, if cond then Printers.default else Printers.noPrinter, show)(op) else op - inline def apply[T](inline question: String, inline printer: Printers.Printer, inline showOp: Any => String)(op: => T)(using Context): T = - inline if (isForced || Config.tracingEnabled) { - if (!isForced && printer.eq(config.Printers.noPrinter)) op - else doTrace[T](question, printer, showOp)(op) - } + inline def apply[T](inline question: String, inline printer: Printers.Printer, inline showOp: Any => String)(inline op: T)(using Context): T = + if Config.tracingEnabled then + doTrace[T](question, printer, showOp)(op) else op - inline def apply[T](inline question: String, inline printer: Printers.Printer, inline show: Boolean)(op: => T)(using Context): T = - inline if (isForced || Config.tracingEnabled) { - if (!isForced && printer.eq(config.Printers.noPrinter)) op - else doTrace[T](question, printer, if (show) showShowable(_) else alwaysToString)(op) - } + inline def apply[T](inline question: String, inline printer: Printers.Printer, inline show: Boolean)(inline op: T)(using Context): T = + if Config.tracingEnabled then + doTrace[T](question, printer, if show then showShowable(_) else alwaysToString)(op) else op inline def apply[T](inline question: String, inline printer: Printers.Printer)(inline op: T)(using Context): T = @@ -56,55 +38,41 @@ abstract class TraceSyntax { apply[T](question, Printers.default, show)(op) inline def apply[T](inline question: String)(inline op: T)(using Context): T = - apply[T](question, Printers.default, false)(op) + apply[T](question, false)(op) - private def showShowable(x: Any)(using Context) = x match { + private def showShowable(x: Any)(using Context) = x match case x: printing.Showable => x.show case _ => String.valueOf(x) - } private val alwaysToString = (x: Any) => String.valueOf(x) private def doTrace[T](question: => String, printer: Printers.Printer = Printers.default, showOp: Any => String = alwaysToString) - (op: => T)(using Context): T = { - // Avoid evaluating question multiple time, since each evaluation - // may cause some extra logging output. - lazy val q: String = question - apply[T](s"==> $q?", (res: Any) => s"<== $q = ${showOp(res)}")(op) - } - - def apply[T](leading: => String, trailing: Any => String)(op: => T)(using Context): T = { - val log: String => Unit = if (isForced) Console.println else { - var logctx = ctx - while (logctx.reporter.isInstanceOf[StoreReporter]) logctx = logctx.outer - report.log(_)(using logctx) - } - doApply(leading, trailing, log)(op) - } - - def doApply[T](leading: => String, trailing: Any => String, log: String => Unit)(op: => T)(using Context): T = - if (ctx.mode.is(Mode.Printing)) op - else { + (op: => T)(using Context): T = + if ctx.mode.is(Mode.Printing) || (printer eq Printers.noPrinter) then op + else + // Avoid evaluating question multiple time, since each evaluation + // may cause some extra logging output. + val q = question + val leading = s"==> $q?" + val trailing = (res: Any) => s"<== $q = ${showOp(res)}" var finalized = false + var logctx = ctx + while logctx.reporter.isInstanceOf[StoreReporter] do logctx = logctx.outer + def margin = ctx.base.indentTab * ctx.base.indent def finalize(result: Any, note: String) = - if (!finalized) { + if !finalized then ctx.base.indent -= 1 - log(s"${ctx.base.indentTab * ctx.base.indent}${trailing(result)}$note") + report.log(s"$margin${trailing(result)}$note") finalized = true - } - try { - log(s"${ctx.base.indentTab * ctx.base.indent}$leading") + try + report.log(s"$margin$leading") ctx.base.indent += 1 val res = op finalize(res, "") res - } - catch { - case ex: Throwable => - finalize("", s" (with exception $ex)") - throw ex - } - } -} + catch case ex: Throwable => + finalize("", s" (with exception $ex)") + throw ex +end trace