From a6a7c2847be6cfa500f49e2f28f574db1fbca120 Mon Sep 17 00:00:00 2001 From: Rikito Taniguchi Date: Mon, 10 Aug 2026 13:32:34 +0900 Subject: [PATCH 1/2] Cache asSeenFrom results per context --- .../src/dotty/tools/dotc/core/Contexts.scala | 23 ++++++++++++++++++- .../src/dotty/tools/dotc/core/TypeOps.scala | 15 +++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 049d6d06a466..c72006ca8e9f 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -14,7 +14,7 @@ import Uniques.* import ast.Trees.* import Flags.ParamAccessor import ast.untpd -import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet, WrappedSourceFile} +import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet, WrappedSourceFile, GenericHashMap} import typer.{Implicits, ImportInfo, SearchHistory, SearchRoot, TypeAssigner, Typer, Nullables} import inlines.Inliner import Nullables.* @@ -275,6 +275,26 @@ object Contexts { None ) + /** A hash map whose composite keys compare each component by identity. */ + private[dotc] class AsSeenFromCache + extends GenericHashMap[(Type, Type, Symbol), Type](8, 2) { // same as EqHashSet + protected def hash(key: (Type, Type, Symbol)): Int = { + (System.identityHashCode(key._1) * 31 + + System.identityHashCode(key._2)) * 31 + + System.identityHashCode(key._3) + } + + protected def isEqual(x: (Type, Type, Symbol), y: (Type, Type, Symbol)): Boolean = + (x._1 eq y._1) && (x._2 eq y._2) && (x._3 eq y._3) + } + + /** Cache for `TypeOps.asSeenFrom` results computed in this context. */ + private var asSeenFromCacheMap: AsSeenFromCache | Null = null + private[dotc] def asSeenFromCache: AsSeenFromCache = + if asSeenFromCacheMap == null then + asSeenFromCacheMap = new AsSeenFromCache + asSeenFromCacheMap.nn + private var related: SimpleIdentityMap[Phase | SourceFile, Context] | Null = null private def lookup(key: Phase | SourceFile): Context | Null = @@ -551,6 +571,7 @@ object Contexts { protected def resetCaches(): Unit = implicitsCache = null related = null + asSeenFromCacheMap = null /** Reuse this context as a fresh context nested inside `outer` * But keep the typerstate, this one has to be set explicitly if needed. diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 82f04a61b0f7..f27a88e90af9 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -53,7 +53,20 @@ object TypeOps: case _ => } - new AsSeenFromMap(pre, cls).apply(tp) + if tp.isProvisional || pre.isProvisional then + new AsSeenFromMap(pre, cls).apply(tp) + else + val cache = ctx.asSeenFromCache + val key = (tp, pre, cls) + val cached = cache.lookup(key) + if cached != null then + Stats.record("asSeenFrom cache hit") + cached + else + Stats.record("asSeenFrom cache miss") + val res = new AsSeenFromMap(pre, cls).apply(tp) + cache.update(key, res) + res } /** The TypeMap handling the asSeenFrom */ From 07174417b041a5e214e1becd520afc4e7485a545 Mon Sep 17 00:00:00 2001 From: Rikito Taniguchi Date: Wed, 12 Aug 2026 11:39:24 +0900 Subject: [PATCH 2/2] Simplify asSeenFromMap and use getOrElseUpdate --- .../src/dotty/tools/dotc/core/Contexts.scala | 33 +++++++++---------- .../src/dotty/tools/dotc/core/TypeOps.scala | 18 ++++------ 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index c72006ca8e9f..d4d1415a6c8c 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -14,7 +14,7 @@ import Uniques.* import ast.Trees.* import Flags.ParamAccessor import ast.untpd -import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet, WrappedSourceFile, GenericHashMap} +import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet, WrappedSourceFile, HashMap} import typer.{Implicits, ImportInfo, SearchHistory, SearchRoot, TypeAssigner, Typer, Nullables} import inlines.Inliner import Nullables.* @@ -125,6 +125,18 @@ object Contexts { * of all class fields of type context; allow them only in allowlisted * classes (which should be short-lived). */ + + /** Identity-based key for the per-context `asSeenFrom` cache. */ + private[dotc] final class AsfKey(val tp: Type, val pre: Type, val cls: Symbol): + override def hashCode: Int = + ((System.identityHashCode(tp) * 31 + + System.identityHashCode(pre)) * 31 + + System.identityHashCode(cls)) + override def equals(other: Any): Boolean = other match + case that: AsfKey => + (tp eq that.tp) && (pre eq that.pre) && (cls eq that.cls) + case _ => false + abstract class Context(val base: ContextBase) { thiscontext => protected given Context = this @@ -275,24 +287,11 @@ object Contexts { None ) - /** A hash map whose composite keys compare each component by identity. */ - private[dotc] class AsSeenFromCache - extends GenericHashMap[(Type, Type, Symbol), Type](8, 2) { // same as EqHashSet - protected def hash(key: (Type, Type, Symbol)): Int = { - (System.identityHashCode(key._1) * 31 - + System.identityHashCode(key._2)) * 31 - + System.identityHashCode(key._3) - } - - protected def isEqual(x: (Type, Type, Symbol), y: (Type, Type, Symbol)): Boolean = - (x._1 eq y._1) && (x._2 eq y._2) && (x._3 eq y._3) - } - /** Cache for `TypeOps.asSeenFrom` results computed in this context. */ - private var asSeenFromCacheMap: AsSeenFromCache | Null = null - private[dotc] def asSeenFromCache: AsSeenFromCache = + private var asSeenFromCacheMap: HashMap[AsfKey, Type] | Null = null + private[dotc] def asSeenFromCache: HashMap[AsfKey, Type] = if asSeenFromCacheMap == null then - asSeenFromCacheMap = new AsSeenFromCache + asSeenFromCacheMap = HashMap() asSeenFromCacheMap.nn private var related: SimpleIdentityMap[Phase | SourceFile, Context] | Null = null diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index f27a88e90af9..8b7f6aba3729 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -56,17 +56,13 @@ object TypeOps: if tp.isProvisional || pre.isProvisional then new AsSeenFromMap(pre, cls).apply(tp) else - val cache = ctx.asSeenFromCache - val key = (tp, pre, cls) - val cached = cache.lookup(key) - if cached != null then - Stats.record("asSeenFrom cache hit") - cached - else - Stats.record("asSeenFrom cache miss") - val res = new AsSeenFromMap(pre, cls).apply(tp) - cache.update(key, res) - res + var hit = true + val res = ctx.asSeenFromCache.getOrElseUpdate(AsfKey(tp, pre, cls), { + hit = false + new AsSeenFromMap(pre, cls).apply(tp) + }) + Stats.record(if hit then "asSeenFrom cache hit" else "asSeenFrom cache miss") + res } /** The TypeMap handling the asSeenFrom */