Skip to content

Commit d49c2d9

Browse files
authored
Performance optimization: only compute stack traces when needed (#16616)
I noticed that `Throwable#fillInStackTrace` (which is called from the constructor of Throwable) showed up while profiling the compiler. This is because several data structures we use (`Diagnostic` and `TypeError`) extend `Exception`. Since the stack trace is only used for debugging in some rare cases and is expensive to compute, it's worth avoiding it whenever possible. This can be done either by passing `false` to the `writableStackTrace` constructor parameter, by overriding `Throwable#fillInStackTrace` or by extending `scala.util.control.NoStackTrace`. I ended up not touching `Diagnostic` in this PR because it will be changed in #16566 to not extend `Exception`.
2 parents b65b0f2 + 49947f9 commit d49c2d9

File tree

2 files changed

+16
-3
lines changed

2 files changed

+16
-3
lines changed

compiler/src/dotty/tools/dotc/CompilationUnit.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import core.Decorators._
1616
import config.{SourceVersion, Feature}
1717
import StdNames.nme
1818
import scala.annotation.internal.sharable
19+
import scala.util.control.NoStackTrace
1920
import transform.MacroAnnotations
2021

2122
class CompilationUnit protected (val source: SourceFile) {
@@ -105,7 +106,7 @@ class CompilationUnit protected (val source: SourceFile) {
105106

106107
object CompilationUnit {
107108

108-
class SuspendException extends Exception
109+
class SuspendException extends Exception with NoStackTrace
109110

110111
/** Make a compilation unit for top class `clsd` with the contents of the `unpickled` tree */
111112
def apply(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(using Context): CompilationUnit =

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

+14-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,22 @@ import Denotations._
1212
import Decorators._
1313
import reporting._
1414
import ast.untpd
15-
import config.Printers.cyclicErrors
15+
import config.Printers.{cyclicErrors, noPrinter}
16+
17+
import scala.annotation.constructorOnly
1618

1719
abstract class TypeError(using creationContext: Context) extends Exception(""):
1820

21+
/** Will the stack trace of this exception be filled in?
22+
* This is expensive and only useful for debugging purposes.
23+
*/
24+
def computeStackTrace: Boolean =
25+
ctx.debug || (cyclicErrors != noPrinter && this.isInstanceOf[CyclicReference] && !(ctx.mode is Mode.CheckCyclic))
26+
27+
override def fillInStackTrace(): Throwable =
28+
if computeStackTrace then super.fillInStackTrace().nn
29+
else this
30+
1931
/** Convert to message. This takes an additional Context, so that we
2032
* use the context when the message is first produced, i.e. when the TypeError
2133
* is handled. This makes a difference for CyclicErrors since we need to know
@@ -164,7 +176,7 @@ class CyclicReference private (val denot: SymDenotation)(using Context) extends
164176
object CyclicReference:
165177
def apply(denot: SymDenotation)(using Context): CyclicReference =
166178
val ex = new CyclicReference(denot)
167-
if !(ctx.mode is Mode.CheckCyclic) || ctx.settings.Ydebug.value then
179+
if ex.computeStackTrace then
168180
cyclicErrors.println(s"Cyclic reference involving! $denot")
169181
val sts = ex.getStackTrace.asInstanceOf[Array[StackTraceElement]]
170182
for (elem <- sts take 200)

0 commit comments

Comments
 (0)