Skip to content

Better caching of prototypes #9742

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 8, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 2 additions & 5 deletions compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
102 changes: 35 additions & 67 deletions compiler/src/dotty/tools/dotc/reporting/trace.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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("<missing>", s" (with exception $ex)")
throw ex
}
}
}
catch case ex: Throwable =>
finalize("<missing>", s" (with exception $ex)")
throw ex
end trace
44 changes: 33 additions & 11 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,25 @@ 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

// equals comes from case class; no need to redefine
end IgnoredProto

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 _ => new IgnoredProto(ignored)
case _ => unique(CachedIgnoredProto(ignored))

/** A prototype for expressions [] that are part of a selection operation:
*
Expand Down Expand Up @@ -185,22 +196,27 @@ 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)

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)
case _ =>
false
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the slight difference between equals and eql have a semantic difference or only a performance difference? In any case, for maintainability, it is worth some explanation here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's only for performance.eql does a eq on all recursive calls, knowing that all elements are already hash-consed. I'll add an explanation in one of the next PRs

}

class CachedSelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean)
Expand Down Expand Up @@ -450,6 +466,10 @@ 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
// equals comes from case class; no need to redefine
}

object ViewProto {
Expand Down Expand Up @@ -680,6 +700,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 =>
Expand Down