From 1418c04682dd7354b6ab5f835ea1f2bd31579258 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 5 Aug 2020 17:11:52 +0200 Subject: [PATCH 01/21] Make LinearIdentitySet use our own HashSet Looking at these benchmarks, our own HashSet is clearly the fastest so we should use it wherever possible. First tests without GC: ``` java.util.HashMap took 1977.537756 ms java.util.IdentityHashMap took 2123.028843 ms scala.collection.HashMap took 2116.062172 ms scala.collection.HashSet took 1998.378418 ms dotty.tools.dotc.HashSet took 1429.826866 ms ``` Second tests with System.gc() run before every test: ``` java.util.HashMap took 1980.977419 ms java.util.IdentityHashMap took 2082.178216 ms scala.collection.HashMap took 2101.882151 ms scala.collection.HashSet took 1967.379402 ms dotty.tools.dotc.HashSet took 1450.755273 ms ``` --- tests/pos-with-compiler/benchSets.scala | 146 ++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 tests/pos-with-compiler/benchSets.scala diff --git a/tests/pos-with-compiler/benchSets.scala b/tests/pos-with-compiler/benchSets.scala new file mode 100644 index 000000000000..41088de7f30b --- /dev/null +++ b/tests/pos-with-compiler/benchSets.scala @@ -0,0 +1,146 @@ +type Elem = Object + +def newElem() = new Object() + +val MissFactor = 2 + +val Runs = 100 // number of runs to warm-up, and then number of runs to test +val ItersPerRun = 1000 +val elems = Array.fill(1024 * MissFactor)(newElem()) + +def testJava = + val set = java.util.HashMap[Elem, Elem]() + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set.put(e, e) + i += 1 + while i > 0 do + i -= 1 + if set.containsKey(elems(i)) then + count += 1 + iter += 1 + count + +def testJavaId = + val set = java.util.IdentityHashMap[Elem, Elem]() + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set.put(e, e) + i += 1 + while i > 0 do + i -= 1 + if set.containsKey(elems(i)) then + count += 1 + iter += 1 + count + +def testScalaMap = + val set = scala.collection.mutable.HashMap[Elem, Elem]() + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set.update(e, e) + i += 1 + while i > 0 do + i -= 1 + if set.contains(elems(i)) then + count += 1 + iter += 1 + count + +def testDottySet = + val set = dotty.tools.dotc.util.HashSet[Elem](64) + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set += e + i += 1 + while i > 0 do + i -= 1 + if set.contains(elems(i)) then + count += 1 + iter += 1 + count + +def testScalaSet = + val set = scala.collection.mutable.HashSet[Elem]() + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set += e + i += 1 + while i > 0 do + i -= 1 + if set.contains(elems(i)) then + count += 1 + iter += 1 + count + +def testLinearSet = + var set = dotty.tools.dotc.util.LinearIdentitySet.empty[Elem] + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set += e + i += 1 + while i > 0 do + i -= 1 + if set.contains(elems(i)) then + count += 1 + iter += 1 + count + + +val expected = (elems.size / MissFactor) * ItersPerRun + +def profile(name: String, op: => Int) = + for i <- 0 until 100 do assert(op == expected) + //System.gc() + val start = System.nanoTime() + var count = 0 + for i <- 0 until 100 do count += op + //println(count) + assert(count == expected * Runs) + println(f"$name took ${(System.nanoTime().toDouble - start)/1_000_000} ms") + +@main def Test = + profile("java.util.HashMap ", testJava) + profile("java.util.IdentityHashMap ", testJavaId) + profile("scala.collection.HashMap ", testScalaMap) + profile("scala.collection.HashSet ", testScalaSet) + profile("dotty.tools.dotc.HashSet ", testDottySet) + profile("dotty.tools.dotc.LinearSet", testLinearSet) + + profile("dotty.tools.dotc.LinearSet", testLinearSet) + profile("dotty.tools.dotc.HashSet ", testDottySet) + profile("scala.collection.HashSet ", testScalaSet) + profile("scala.collection.HashMap ", testScalaMap) + profile("java.util.IdentityHashMap ", testJavaId) + profile("java.util.HashMap ", testJava) + From 4d1773960842665d332da0c1a450b0bc5e4d081e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 23 Aug 2020 11:21:29 +0200 Subject: [PATCH 02/21] Instrument collection sizes Allow to record total sizes of collections on which some selected operation is performed. # Conflicts: # compiler/src/dotty/tools/dotc/transform/Instrumentation.scala --- .../dotty/tools/dotc/core/Definitions.scala | 2 - .../dotc/transform/Instrumentation.scala | 47 ++++++++++++++----- .../src/dotty/tools/dotc/util/Stats.scala | 4 ++ 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 8263bab70aca..234f1b174919 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -844,8 +844,6 @@ class Definitions { @tu lazy val Not_value: Symbol = NotClass.companionModule.requiredMethod(nme.value) @tu lazy val ValueOfClass: ClassSymbol = requiredClass("scala.ValueOf") - @tu lazy val StatsModule: Symbol = requiredModule("dotty.tools.dotc.util.Stats") - @tu lazy val Stats_doRecord: Symbol = StatsModule.requiredMethod("doRecord") @tu lazy val FromDigitsClass: ClassSymbol = requiredClass("scala.util.FromDigits") @tu lazy val FromDigits_WithRadixClass: ClassSymbol = requiredClass("scala.util.FromDigits.WithRadix") diff --git a/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala b/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala index 5f8f9c91faf3..b4d2c541f2c5 100644 --- a/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala +++ b/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala @@ -1,4 +1,5 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package transform import core._ @@ -27,25 +28,49 @@ class Instrumentation extends MiniPhase { thisPhase => override def isEnabled(using Context) = ctx.settings.Yinstrument.value - private val namesOfInterest = List( - "::", "+=", "toString", "newArray", "box", "toCharArray", - "map", "flatMap", "filter", "withFilter", "collect", "foldLeft", "foldRight", "take", - "reverse", "mapConserve", "mapconserve", "filterConserve", "zip", - "denotsNamed", "lookup", "lookupEntry", "lookupAll", "toList") - private var namesToRecord: Set[Name] = _ + private val collectionNamesOfInterest = List( + "map", "flatMap", "filter", "filterNot", "withFilter", "collect", "flatten", "foldLeft", "foldRight", "take", + "reverse", "zip", "++", ":::", ":+", "distinct", "dropRight", "takeRight", "groupBy", "groupMap", "init", "inits", + "interect", "mkString", "partition", "reverse_:::", "scanLeft", "scanRight", + "sortBy", "sortWith", "sorted", "span", "splitAt", "takeWhile", "transpose", "unzip", "unzip3", + "updated", "zipAll", "zipWithIndex", + "mapConserve", "mapconserve", "filterConserve", "zipWithConserve", "mapWithIndexConserve" + ) + + private val namesOfInterest = collectionNamesOfInterest ++ List( + "::", "+=", "toString", "newArray", "box", "toCharArray", "termName", "typeName", + "slice", "staticRef", "requiredClass") - private var consName: TermName = _ - private var consEqName: TermName = _ + private var namesToRecord: Set[Name] = _ + private var collectionNamesToRecord: Set[Name] = _ + private var Stats_doRecord: Symbol = _ + private var Stats_doRecordSize: Symbol = _ + private var CollectionIterableClass: ClassSymbol = _ override def prepareForUnit(tree: Tree)(using Context): Context = namesToRecord = namesOfInterest.map(_.toTermName).toSet + collectionNamesToRecord = collectionNamesOfInterest.map(_.toTermName).toSet + val StatsModule = requiredModule("dotty.tools.dotc.util.Stats") + Stats_doRecord = StatsModule.requiredMethod("doRecord") + Stats_doRecordSize = StatsModule.requiredMethod("doRecordSize") + CollectionIterableClass = requiredClass("scala.collection.Iterable") ctx private def record(category: String, tree: Tree)(using Context): Tree = { val key = Literal(Constant(s"$category@${tree.sourcePos.show}")) - ref(defn.Stats_doRecord).appliedTo(key, Literal(Constant(1))) + ref(Stats_doRecord).appliedTo(key, Literal(Constant(1))) } + private def recordSize(tree: Apply)(using Context): Tree = tree.fun match + case sel @ Select(qual, name) + if collectionNamesToRecord.contains(name) + && qual.tpe.widen.derivesFrom(CollectionIterableClass) => + val key = Literal(Constant(s"totalSize/${name} in ${qual.tpe.widen.classSymbol.name}@${tree.sourcePos.show}")) + val qual1 = ref(Stats_doRecordSize).appliedTo(key, qual).cast(qual.tpe.widen) + cpy.Apply(tree)(cpy.Select(sel)(qual1, name), tree.args) + case _ => + tree + private def ok(using Context) = !ctx.owner.ownersIterator.exists(_.name.toString.startsWith("Stats")) @@ -53,7 +78,7 @@ class Instrumentation extends MiniPhase { thisPhase => case Select(nu: New, _) => cpy.Block(tree)(record(i"alloc/${nu.tpe}", tree) :: Nil, tree) case ref: RefTree if namesToRecord.contains(ref.name) && ok => - cpy.Block(tree)(record(i"call/${ref.name}", tree) :: Nil, tree) + cpy.Block(tree)(record(i"call/${ref.name}", tree) :: Nil, recordSize(tree)) case _ => tree } diff --git a/compiler/src/dotty/tools/dotc/util/Stats.scala b/compiler/src/dotty/tools/dotc/util/Stats.scala index ecbf266866eb..684083fa77ba 100644 --- a/compiler/src/dotty/tools/dotc/util/Stats.scala +++ b/compiler/src/dotty/tools/dotc/util/Stats.scala @@ -28,6 +28,10 @@ import collection.mutable hits(name) += n } + def doRecordSize(fn: String, coll: scala.collection.Iterable[_]): coll.type = + doRecord(fn, coll.size) + coll + inline def trackTime[T](fn: String)(inline op: T): T = if (enabled) doTrackTime(fn)(op) else op From 5cc5375e1141b0fe362a5f5a6ab16dc064898961 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 23 Aug 2020 11:56:03 +0200 Subject: [PATCH 03/21] Avoid some expensive collection operations in backend --- .../tools/backend/jvm/BCodeBodyBuilder.scala | 12 +++-- .../tools/backend/jvm/BCodeIdiomatic.scala | 8 ++-- .../tools/backend/jvm/BCodeSkelBuilder.scala | 12 ++--- .../src/dotty/tools/backend/jvm/BTypes.scala | 7 ++- .../tools/backend/jvm/BTypesFromSymbols.scala | 47 +++++++++---------- .../dotty/tools/backend/jvm/GenBCodeOps.scala | 3 +- 6 files changed, 46 insertions(+), 43 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index 593645f0113e..407dc47733ce 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -1021,9 +1021,15 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { } } - def genLoadArguments(args: List[Tree], btpes: List[BType]): Unit = { - (args zip btpes) foreach { case (arg, btpe) => genLoad(arg, btpe) } - } + def genLoadArguments(args: List[Tree], btpes: List[BType]): Unit = + args match + case arg :: args1 => + btpes match + case btpe :: btpes1 => + genLoad(arg, btpe) + genLoadArguments(args1, btpes1) + case _ => + case _ => def genLoadModule(tree: Tree): BType = { val module = ( diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala index 708a6fee9fff..4112dcbeda20 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala @@ -35,10 +35,10 @@ trait BCodeIdiomatic { lazy val majorVersion: Int = (classfileVersion & 0xFF) lazy val emitStackMapFrame = (majorVersion >= 50) - val extraProc: Int = GenBCodeOps.mkFlags( - asm.ClassWriter.COMPUTE_MAXS, - if (emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0 - ) + val extraProc: Int = + import GenBCodeOps.addFlagIf + asm.ClassWriter.COMPUTE_MAXS + .addFlagIf(emitStackMapFrame, asm.ClassWriter.COMPUTE_FRAMES) lazy val JavaStringBuilderClassName = jlStringBuilderRef.internalName diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 9a1bafa423c7..3dd7230b1875 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -690,12 +690,12 @@ trait BCodeSkelBuilder extends BCodeHelpers { val isNative = methSymbol.hasAnnotation(NativeAttr) val isAbstractMethod = (methSymbol.is(Deferred) || (methSymbol.owner.isInterface && ((methSymbol.is(Deferred)) || methSymbol.isClassConstructor))) - val flags = GenBCodeOps.mkFlags( - javaFlags(methSymbol), - if (isAbstractMethod) asm.Opcodes.ACC_ABSTRACT else 0, - if (false /*methSymbol.isStrictFP*/) asm.Opcodes.ACC_STRICT else 0, - if (isNative) asm.Opcodes.ACC_NATIVE else 0 // native methods of objects are generated in mirror classes - ) + val flags = + import GenBCodeOps.addFlagIf + javaFlags(methSymbol) + .addFlagIf(isAbstractMethod, asm.Opcodes.ACC_ABSTRACT) + .addFlagIf(false /*methSymbol.isStrictFP*/, asm.Opcodes.ACC_STRICT) + .addFlagIf(isNative, asm.Opcodes.ACC_NATIVE) // native methods of objects are generated in mirror classes // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize } initJMethod(flags, params.map(p => p.symbol.annotations)) diff --git a/compiler/src/dotty/tools/backend/jvm/BTypes.scala b/compiler/src/dotty/tools/backend/jvm/BTypes.scala index b49b97c58ec9..fb399461a03c 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypes.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypes.scala @@ -649,14 +649,13 @@ abstract class BTypes { def innerClassAttributeEntry: Option[InnerClassEntry] = info.nestedInfo map { case NestedInfo(_, outerName, innerName, isStaticNestedClass) => + import GenBCodeOps.addFlagIf InnerClassEntry( internalName, outerName.orNull, innerName.orNull, - GenBCodeOps.mkFlags( - info.flags, - if (isStaticNestedClass) asm.Opcodes.ACC_STATIC else 0 - ) & ClassBType.INNER_CLASSES_FLAGS + info.flags.addFlagIf(isStaticNestedClass, asm.Opcodes.ACC_STATIC) + & ClassBType.INNER_CLASSES_FLAGS ) } diff --git a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala index beea05e0dcc1..7fb716b00782 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala @@ -304,39 +304,36 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes { val finalFlag = sym.is(Final) && !toDenot(sym).isClassConstructor && !sym.is(Mutable) && !sym.enclosingClass.is(Trait) import asm.Opcodes._ - GenBCodeOps.mkFlags( - if (privateFlag) ACC_PRIVATE else ACC_PUBLIC, - if (sym.is(Deferred) || sym.isOneOf(AbstractOrTrait)) ACC_ABSTRACT else 0, - if (sym.isInterface) ACC_INTERFACE else 0, - - if (finalFlag && + import GenBCodeOps.addFlagIf + 0 .addFlagIf(privateFlag, ACC_PRIVATE) + .addFlagIf(!privateFlag, ACC_PUBLIC) + .addFlagIf(sym.is(Deferred) || sym.isOneOf(AbstractOrTrait), ACC_ABSTRACT) + .addFlagIf(sym.isInterface, ACC_INTERFACE) + .addFlagIf(finalFlag // Primitives are "abstract final" to prohibit instantiation // without having to provide any implementations, but that is an // illegal combination of modifiers at the bytecode level so // suppress final if abstract if present. - !sym.isOneOf(AbstractOrTrait) && + && !sym.isOneOf(AbstractOrTrait) // Mixin forwarders are bridges and can be final, but final bridges confuse some frameworks - !sym.is(Bridge)) - ACC_FINAL else 0, - - if (sym.isStaticMember) ACC_STATIC else 0, - if (sym.is(Bridge)) ACC_BRIDGE | ACC_SYNTHETIC else 0, - if (sym.is(Artifact)) ACC_SYNTHETIC else 0, - if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, - if (sym.isAllOf(JavaEnumTrait)) ACC_ENUM else 0, - if (sym.is(JavaVarargs)) ACC_VARARGS else 0, - if (sym.is(Synchronized)) ACC_SYNCHRONIZED else 0, - if (sym.isDeprecated) ACC_DEPRECATED else 0, - if (sym.is(Enum)) ACC_ENUM else 0 - ) + && !sym.is(Bridge), ACC_FINAL) + .addFlagIf(sym.isStaticMember, ACC_STATIC) + .addFlagIf(sym.is(Bridge), ACC_BRIDGE | ACC_SYNTHETIC) + .addFlagIf(sym.is(Artifact), ACC_SYNTHETIC) + .addFlagIf(sym.isClass && !sym.isInterface, ACC_SUPER) + .addFlagIf(sym.isAllOf(JavaEnumTrait), ACC_ENUM) + .addFlagIf(sym.is(JavaVarargs), ACC_VARARGS) + .addFlagIf(sym.is(Synchronized), ACC_SYNCHRONIZED) + .addFlagIf(sym.isDeprecated, ACC_DEPRECATED) + .addFlagIf(sym.is(Enum), ACC_ENUM) } def javaFieldFlags(sym: Symbol) = { import asm.Opcodes._ - javaFlags(sym) | GenBCodeOps.mkFlags( - if (sym.hasAnnotation(TransientAttr)) ACC_TRANSIENT else 0, - if (sym.hasAnnotation(VolatileAttr)) ACC_VOLATILE else 0, - if (sym.is(Mutable)) 0 else ACC_FINAL - ) + import GenBCodeOps.addFlagIf + javaFlags(sym) + .addFlagIf(sym.hasAnnotation(TransientAttr), ACC_TRANSIENT) + .addFlagIf(sym.hasAnnotation(VolatileAttr), ACC_VOLATILE) + .addFlagIf(!sym.is(Mutable), ACC_FINAL) } } diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCodeOps.scala b/compiler/src/dotty/tools/backend/jvm/GenBCodeOps.scala index 884b86751b03..210e47566cb9 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCodeOps.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCodeOps.scala @@ -7,7 +7,8 @@ import scala.tools.asm object GenBCodeOps extends GenBCodeOps class GenBCodeOps { - def mkFlags(args: Int*) = args.foldLeft(0)(_ | _) + extension (flags: Int) + def addFlagIf(cond: Boolean, flag: Int): Int = if cond then flags | flag else flags final val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC final val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL From e90da064abf903ff9ba031f0661098ff0565dad6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 23 Aug 2020 12:13:43 +0200 Subject: [PATCH 04/21] Avoid redundant filter in memberNames --- compiler/src/dotty/tools/dotc/core/Types.scala | 16 +++++++++++++++- .../tools/dotc/interactive/Completion.scala | 1 + .../src/dotty/tools/dotc/typer/RefChecks.scala | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8c3883cfd2f6..ba2adf7701e4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -786,7 +786,8 @@ object Types { */ final def memberNames(keepOnly: NameFilter, pre: Type = this)(using Context): Set[Name] = this match { case tp: ClassInfo => - tp.cls.classDenot.memberNames(keepOnly) filter (keepOnly(pre, _)) + val names = tp.cls.classDenot.memberNames(keepOnly) + if keepOnly.isStable then names else names.filter(keepOnly(pre, _)) case tp: RefinedType => val ns = tp.parent.memberNames(keepOnly, pre) if (keepOnly(pre, tp.refinedName)) ns + tp.refinedName else ns @@ -5642,6 +5643,11 @@ object Types { */ abstract class NameFilter { def apply(pre: Type, name: Name)(using Context): Boolean + + /** Filter does not need to be rechecked with full prefix, if it + * has been already checked for the class denotation of the prefix + */ + def isStable: Boolean } /** A filter for names of abstract types of a given type */ @@ -5651,6 +5657,7 @@ object Types { val mbr = pre.nonPrivateMember(name) mbr.symbol.is(Deferred) && mbr.info.isInstanceOf[RealTypeBounds] } + def isStable = false } /** A filter for names of abstract types of a given type */ @@ -5660,12 +5667,14 @@ object Types { val mbr = pre.member(name) mbr.symbol.isType && !mbr.symbol.isClass } + def isStable = false } /** A filter for names of deferred term definitions of a given type */ object abstractTermNameFilter extends NameFilter { def apply(pre: Type, name: Name)(using Context): Boolean = name.isTermName && pre.nonPrivateMember(name).hasAltWith(_.symbol.is(Deferred)) + def isStable = false } /** A filter for names of type aliases of a given type */ @@ -5675,19 +5684,23 @@ object Types { val mbr = pre.nonPrivateMember(name) mbr.symbol.isAliasType } + def isStable = false } object typeNameFilter extends NameFilter { def apply(pre: Type, name: Name)(using Context): Boolean = name.isTypeName + def isStable = true } object fieldFilter extends NameFilter { def apply(pre: Type, name: Name)(using Context): Boolean = name.isTermName && (pre member name).hasAltWith(!_.symbol.is(Method)) + def isStable = true } object takeAllFilter extends NameFilter { def apply(pre: Type, name: Name)(using Context): Boolean = true + def isStable = true } object implicitFilter extends NameFilter { @@ -5696,6 +5709,7 @@ object Types { * no post-filtering is needed. */ def apply(pre: Type, name: Name)(using Context): Boolean = true + def isStable = true } // ----- Debug --------------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 79c4f52fb0ba..a4070bca858a 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -319,6 +319,7 @@ object Completion { private object completionsFilter extends NameFilter { def apply(pre: Type, name: Name)(using Context): Boolean = !name.isConstructorName && name.toTermName.info.kind == SimpleNameKind + def isStable = true } } diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index c85aee7aa65b..30b52eaabecd 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -29,6 +29,7 @@ object RefChecks { private val defaultMethodFilter = new NameFilter { def apply(pre: Type, name: Name)(using Context): Boolean = name.is(DefaultGetterName) + def isStable = true } /** Only one overloaded alternative is allowed to define default arguments */ From 10bf6fbd44cad7ad164bad05ce95a496fc44c564 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 23 Aug 2020 12:38:21 +0200 Subject: [PATCH 05/21] Add nestedExists extension method Add nestedExists extension method for List[List[T]] exists and optimize it as well as nestedMap. --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 4 ++-- compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala | 2 +- compiler/src/dotty/tools/dotc/core/Decorators.scala | 8 ++++++-- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- .../src/dotty/tools/dotc/transform/ElimRepeated.scala | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 19f8cc586279..34237f3d04e2 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -594,9 +594,9 @@ object desugar { (ordinalMethLit(ordinal) :: enumLabelLit(className.toString) :: Nil, scaffolding) else (Nil, Nil) def copyMeths = { - val hasRepeatedParam = constrVparamss.exists(_.exists { + val hasRepeatedParam = constrVparamss.nestedExists { case ValDef(_, tpt, _) => isRepeated(tpt) - }) + } if (mods.is(Abstract) || hasRepeatedParam) Nil // cannot have default arguments for repeated parameters, hence copy method is not issued else { def copyDefault(vparam: ValDef) = diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 4ed857678b26..74a169126eaa 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -297,7 +297,7 @@ object DesugarEnums { case parent => parent.isType && typeHasRef(parent) } - vparamss.exists(_.exists(valDefHasRef)) || parents.exists(parentHasRef) + vparamss.nestedExists(valDefHasRef) || parents.exists(parentHasRef) } /** A pair consisting of diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 8a9ed11fca03..7e6fb0c7f252 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -195,12 +195,16 @@ object Decorators { } extension [T, U](xss: List[List[T]]) - def nestedMap(f: T => U): List[List[U]] = - xss.map(_.map(f)) + def nestedMap(f: T => U): List[List[U]] = xss match + case xs :: xss1 => xs.map(f) :: xss1.nestedMap(f) + case nil => Nil def nestedMapConserve(f: T => U): List[List[U]] = xss.mapconserve(_.mapconserve(f)) def nestedZipWithConserve(yss: List[List[U]])(f: (T, U) => T): List[List[T]] = xss.zipWithConserve(yss)((xs, ys) => xs.zipWithConserve(ys)(f)) + def nestedExists(p: T => Boolean): Boolean = xss match + case xs :: xss1 => xs.exists(p) || xss1.nestedExists(p) + case nil => false end extension extension (text: Text) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index a8c1a1da3078..51ab7998338f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -892,7 +892,7 @@ object SymDenotations { else if is(NoDefaultParams) then false else val result = - rawParamss.exists(_.exists(_.is(HasDefault))) + rawParamss.nestedExists(_.is(HasDefault)) || allOverriddenSymbols.exists(_.hasDefaultParams) setFlag(if result then HasDefaultParams else NoDefaultParams) result diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 5aba3e43e368..f31dfe675d88 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3856,7 +3856,7 @@ object Parsers { val problem = tree match case tree: MemberDef if !(tree.mods.flags & ModifierFlags).isEmpty => i"refinement cannot be ${(tree.mods.flags & ModifierFlags).flagStrings().mkString("`", "`, `", "`")}" - case tree: DefDef if tree.vparamss.exists(_.exists(!_.rhs.isEmpty)) => + case tree: DefDef if tree.vparamss.nestedExists(!_.rhs.isEmpty) => i"refinement cannot have default arguments" case tree: ValOrDefDef => if tree.rhs.isEmpty then "" diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index 85e2a62c9172..ecf2995cf30a 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -237,7 +237,7 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => /** Is there a repeated parameter in some parameter list? */ private def hasRepeatedParams(sym: Symbol)(using Context): Boolean = - sym.info.paramInfoss.flatten.exists(_.isRepeatedParam) + sym.info.paramInfoss.nestedExists(_.isRepeatedParam) /** Is this the type of a method that has a repeated parameter type as * its last parameter in the last parameter list? From f4e68446f9ca781eb602bba6c46adcafe5ee66ef Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 23 Aug 2020 13:24:01 +0200 Subject: [PATCH 06/21] Avoid more collection ops in backend --- .../tools/backend/jvm/BCodeHelpers.scala | 19 +++++++++---------- .../tools/backend/jvm/scalaPrimitives.scala | 15 +++++++-------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala index 34ec2b2bb747..3c4f77df074f 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala @@ -339,16 +339,15 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * must-single-thread */ def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[Annotation]]): Unit = - val annotationss = pannotss map (_ filter shouldEmitAnnotation) - if (annotationss forall (_.isEmpty)) return - for ((annots, idx) <- annotationss.zipWithIndex; - annot <- annots) { - val typ = annot.tree.tpe - val assocs = assocsFromApply(annot.tree) - val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, typeDescriptor(typ.asInstanceOf[Type]), isRuntimeVisible(annot)) - emitAssocs(pannVisitor, assocs, BCodeHelpers.this)(this) - } - + if pannotss.nestedExists(shouldEmitAnnotation) then + for (annots, idx) <- pannotss.zipWithIndex + annot <- annots + if shouldEmitAnnotation(annot) + do + val typ = annot.tree.tpe + val assocs = assocsFromApply(annot.tree) + val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, typeDescriptor(typ.asInstanceOf[Type]), isRuntimeVisible(annot)) + emitAssocs(pannVisitor, assocs, BCodeHelpers.this)(this) private def shouldEmitAnnotation(annot: Annotation): Boolean = { annot.symbol.is(JavaDefined) && diff --git a/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala b/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala index e1b6d8d922f7..72f9675c7b09 100644 --- a/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala +++ b/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala @@ -133,16 +133,15 @@ class DottyPrimitives(ictx: Context) { } def addPrimitives(cls: Symbol, method: TermName, code: Int)(using Context): Unit = { - val alts = cls.info.member(method).alternatives.map(_.symbol) - if (alts.isEmpty) + val alts = cls.info.member(method).alternatives + if alts.isEmpty then report.error(s"Unknown primitive method $cls.$method") - else alts foreach (s => + else for d <- alts do + val s = d.symbol addPrimitive(s, - s.info.paramInfoss match { - case List(tp :: _) if code == ADD && tp =:= ctx.definitions.StringType => CONCAT - case _ => code - } - ) + s.info.paramInfoss match + case (tp :: _) :: Nil if code == ADD && tp =:= ctx.definitions.StringType => CONCAT + case _ => code ) } From 7ee269111129833778787594f13b19654bf2df1e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 23 Aug 2020 13:28:28 +0200 Subject: [PATCH 07/21] Avoid more collection ops in frontend --- .../tools/dotc/core/SymDenotations.scala | 8 +++++- .../tools/dotc/core/tasty/NameBuffer.scala | 8 +++--- .../dotty/tools/dotc/transform/SymUtils.scala | 11 ++++++-- .../dotty/tools/dotc/typer/RefChecks.scala | 7 +++--- .../src/dotty/tools/dotc/typer/Typer.scala | 25 +++++++++++-------- 5 files changed, 37 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 51ab7998338f..9f6a9abd2616 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1549,7 +1549,13 @@ object SymDenotations { completeChildrenIn(companionClass) setFlag(ChildrenQueried) - annotations.collect { case Annotation.Child(child) => child }.reverse + /** The children recorded in `annots`, in reverse order */ + def getChildren(annots: List[Annotation], acc: List[Symbol]): List[Symbol] = annots match + case Annotation.Child(child) :: annots1 => getChildren(annots1, child :: acc) + case _ :: annots1 => getChildren(annots1, acc) + case nil => acc + + getChildren(annotations, Nil) end children } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala b/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala index 1294b6fe0a72..aa11d31e2d7f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala @@ -97,14 +97,14 @@ class NameBuffer extends TastyBuffer(10000) { } } - override def assemble(): Unit = { + override def assemble(): Unit = var i = 0 - for ((name, ref) <- nameRefs) { + val nr = nameRefs.iterator + while nr.hasNext do + val (name, ref) = nr.next() assert(ref.index == i) i += 1 pickleNameContents(name) - } - } } object NameBuffer { diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 0d47b19249e7..5a11e6323dd5 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -22,12 +22,19 @@ object SymUtils { extension (self: Symbol) { - /** All traits implemented by a class or trait except for those inherited through the superclass. */ + /** All traits implemented by a class or trait except for those inherited + * through the superclass. Traits are given in the order they appear in the + * parents clause (which is the reverse of their order in baseClasses) + */ def directlyInheritedTraits(using Context): List[ClassSymbol] = { val superCls = self.asClass.superClass val baseClasses = self.asClass.baseClasses if (baseClasses.isEmpty) Nil - else baseClasses.tail.takeWhile(_ ne superCls).reverse + else + def recur(bcs: List[ClassSymbol], acc: List[ClassSymbol]): List[ClassSymbol] = bcs match + case bc :: bcs1 => if bc eq superCls then acc else recur(bcs1, bc :: acc) + case nil => acc + recur(baseClasses.tail, Nil) } /** All traits implemented by a class, except for those inherited through the superclass. diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 30b52eaabecd..215b11a84042 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -491,10 +491,9 @@ object RefChecks { */ def missingTermSymbols: List[Symbol] = val buf = new mutable.ListBuffer[Symbol] - for bc <- clazz.baseClasses - sym <- bc.info.decls.toList - if sym.is(DeferredTerm) && !isImplemented(sym) && !ignoreDeferred(sym) - do buf += sym + for bc <- clazz.baseClasses; sym <- bc.info.decls.toList do + if sym.is(DeferredTerm) && !isImplemented(sym) && !ignoreDeferred(sym) + buf += sym buf.toList // 2. Check that only abstract classes have deferred members diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 5d0b94a45186..463f25382b6b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1223,21 +1223,24 @@ class Typer extends Namer } val desugared = - if (protoFormals.length == 1 && params.length != 1 && ptIsCorrectProduct(protoFormals.head)) { + if (protoFormals.length == 1 && params.length != 1 && ptIsCorrectProduct(protoFormals.head)) val isGenericTuple = protoFormals.head.derivesFrom(defn.TupleClass) && !defn.isTupleClass(protoFormals.head.typeSymbol) desugar.makeTupledFunction(params, fnBody, isGenericTuple) - } - else { - val inferredParams: List[untpd.ValDef] = - for ((param, i) <- params.zipWithIndex) yield - if (!param.tpt.isEmpty) param - else cpy.ValDef(param)( - tpt = untpd.TypeTree( - inferredParamType(param, protoFormal(i)).translateFromRepeated(toArray = false))) - desugar.makeClosure(inferredParams, fnBody, resultTpt, isContextual) - } + else + def inferredParams(params: List[untpd.ValDef], idx: Int): List[untpd.ValDef] = params match + case param :: rest => + val param1 = + if !param.tpt.isEmpty then param + else cpy.ValDef(param)( + tpt = untpd.TypeTree( + inferredParamType(param, protoFormal(idx)).translateFromRepeated(toArray = false))) + param1 :: inferredParams(rest, idx + 1) + case nil => + Nil + desugar.makeClosure(inferredParams(params, 0), fnBody, resultTpt, isContextual) + typed(desugared, pt) } From a84e755604af7886eb28b2343a73e33fde473614 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 24 Aug 2020 18:59:19 +0200 Subject: [PATCH 08/21] Split method types into general, copied, and simple Avoid closures for copied and simple. Avoid substitutions for simple. --- .../src/dotty/tools/dotc/core/Types.scala | 106 ++++++++++++++---- 1 file changed, 87 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ba2adf7701e4..b630947fca97 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3217,14 +3217,15 @@ object Types { if ((paramNames eq this.paramNames) && (paramInfos eq this.paramInfos) && (resType eq this.resType)) this else newLikeThis(paramNames, paramInfos, resType) + protected def substParams(pinfos: List[PInfo], from: LambdaType, to: This)(using Context): List[PInfo] = pinfos match + case pinfos @ (pinfo :: rest) => + pinfos.derivedCons(pinfo.subst(from, to).asInstanceOf[PInfo], substParams(rest, from, to)) + case nil => + nil + def newLikeThis(paramNames: List[ThisName], paramInfos: List[PInfo], resType: Type)(using Context): This = - def substParams(pinfos: List[PInfo], to: This): List[PInfo] = pinfos match - case pinfos @ (pinfo :: rest) => - pinfos.derivedCons(pinfo.subst(this, to).asInstanceOf[PInfo], substParams(rest, to)) - case nil => - nil companion(paramNames)( - x => substParams(paramInfos, x), + x => substParams(paramInfos, this, x), x => resType.subst(this, x)) protected def prefixString: String @@ -3371,17 +3372,10 @@ object Types { else resultType } - abstract case class MethodType(paramNames: List[TermName])( - paramInfosExp: MethodType => List[Type], - resultTypeExp: MethodType => Type) - extends MethodOrPoly with TermLambda with NarrowCached { thisMethodType => + abstract case class MethodType(paramNames: List[TermName]) + extends MethodOrPoly, TermLambda, NarrowCached { thisMethodType => type This = MethodType - - val paramInfos: List[Type] = paramInfosExp(this) - val resType: Type = resultTypeExp(this) - assert(resType.exists) - def companion: MethodTypeCompanion final override def isJavaMethod: Boolean = companion eq JavaMethodType @@ -3397,6 +3391,57 @@ object Types { companion.eq(ContextualMethodType) || companion.eq(ErasedContextualMethodType) + /** A type is _clean_ if a substituting this method type for some other method + * type in a binder substitution yields the type itself. This is the case if: + * - the type does not reference this method type + * - the type does not contain parts that are mapped to something else in + * a type map. In particular, it does not contain instantiated type variables, + * skolems, or LazyRefs. + * The purpose for having this method is to avoid binder substitutions + * if the new method type elements are all known to be clean. + * The method is a conservative approximation. It also returns false for + * some less common types that are not worth chasing down in detail. + */ + private def isClean(tp: Type)(using Context): Boolean = tp match + case tp: NamedType => + tp.symbol.is(Package) || (tp.prefix eq NoPrefix) || isClean(tp.prefix) + case tp: AppliedType => + isClean(tp.tycon) && isClean(tp.args) + case tp: BoundType => + tp.binder ne this + case _: ThisType => + true + case tp: TypeVar => + !tp.instanceOpt.exists + case RefinedType(parent, _, refinedInfo) => + isClean(parent) && isClean(refinedInfo) + case TypeAlias(alias) => + isClean(alias) + case TypeBounds(lo, hi) => + isClean(lo) && isClean(hi) + case tp: AnnotatedType => + isClean(tp.underlying) + case tp: AndOrType => + isClean(tp.tp1) && isClean(tp.tp2) + case WildcardType(bounds) => + isClean(bounds) + case mt: MethodType => + isClean(mt.paramInfos) && isClean(mt.resType) + case _: TypeErasure.ErasedValueType | _: ConstantType | NoType => + true + case _ => + false + + private def isClean(tps: List[Type])(using Context): Boolean = tps match + case tp :: tps1 => isClean(tp) && isClean(tps1) + case nil => true + + final override def newLikeThis(paramNames: List[TermName], paramInfos: List[Type], resType: Type)(using Context): This = + if isClean(resType) && isClean(paramInfos) then + companion(paramNames, paramInfos, resType) + else + companion(paramNames, paramInfos, resType, this) + protected[dotc] def computeSignature(using Context): Signature = { val params = if (isErasedMethod) Nil else paramInfos resultSignature.prependTermParams(params, isJavaMethod) @@ -3405,8 +3450,24 @@ object Types { protected def prefixString: String = companion.prefixString } - final class CachedMethodType(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type, val companion: MethodTypeCompanion) - extends MethodType(paramNames)(paramInfosExp, resultTypeExp) + final class CachedMethodType(paramNames: List[TermName]) + (paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type, + val companion: MethodTypeCompanion) + extends MethodType(paramNames): + val paramInfos: List[Type] = paramInfosExp(this) + val resType: Type = resultTypeExp(this) + assert(resType.exists) + + final class CopiedCachedMethodType(paramNames: List[TermName], + @constructorOnly initParamInfos: List[Type], @constructorOnly initResType: Type, + @constructorOnly from: MethodType, val companion: MethodTypeCompanion)(using @constructorOnly ctx: Context) + extends MethodType(paramNames): + val paramInfos: List[Type] = substParams(initParamInfos, from, this) + val resType: Type = initResType.subst(from, this) + + final class SimpleCachedMethodType(paramNames: List[TermName], + val paramInfos: List[Type], val resType: Type, val companion: MethodTypeCompanion) + extends MethodType(paramNames) abstract class LambdaTypeCompanion[N <: Name, PInfo <: Type, LT <: LambdaType] { def syntheticParamName(n: Int): N @@ -3471,7 +3532,14 @@ object Types { } final def apply(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type)(using Context): MethodType = - checkValid(unique(new CachedMethodType(paramNames)(paramInfosExp, resultTypeExp, self))) + checkValid(unique(CachedMethodType(paramNames)(paramInfosExp, resultTypeExp, self))) + + /** Method type with given fields where references to `origin` are substituted with the result */ + final def apply(paramNames: List[TermName], paramInfos: List[Type], resultType: Type, origin: MethodType)(using Context): MethodType = + checkValid(unique(CopiedCachedMethodType(paramNames, paramInfos, resultType, origin, self))) + + override def apply(paramNames: List[TermName], paramInfos: List[Type], resultType: Type)(using Context): MethodType = + checkValid(unique(SimpleCachedMethodType(paramNames, paramInfos, resultType, self))) def checkValid(mt: MethodType)(using Context): mt.type = { if (Config.checkMethodTypes) @@ -3553,7 +3621,7 @@ object Types { * * Variances are stored in the `typeParams` list of the lambda. */ - class HKTypeLambda(val paramNames: List[TypeName], @constructorOnly variances: List[Variance])( + class HKTypeLambda(val paramNames: List[TypeName], /*@constructorOnly*/ variances: List[Variance])( paramInfosExp: HKTypeLambda => List[TypeBounds], resultTypeExp: HKTypeLambda => Type) extends HKLambda with TypeLambda { type This = HKTypeLambda From b9e948f2df01f8ec4ac31ddc26198ac18937d5d5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 24 Aug 2020 19:02:43 +0200 Subject: [PATCH 09/21] Reduce SourcePosition allocations --- compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala | 2 +- compiler/src/dotty/tools/dotc/util/SourcePosition.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 3dd7230b1875..2c575309d9f1 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -535,7 +535,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { } def lineNumber(tree: Tree): Unit = { if (!emitLines || !tree.span.exists) return; - val nr = ctx.source.atSpan(tree.span).line + 1 + val nr = ctx.source.offsetToLine(tree.span.point) + 1 if (nr != lastEmittedLineNr) { lastEmittedLineNr = nr lastInsn match { diff --git a/compiler/src/dotty/tools/dotc/util/SourcePosition.scala b/compiler/src/dotty/tools/dotc/util/SourcePosition.scala index 79919442a3b2..35d16cff9f44 100644 --- a/compiler/src/dotty/tools/dotc/util/SourcePosition.scala +++ b/compiler/src/dotty/tools/dotc/util/SourcePosition.scala @@ -25,7 +25,7 @@ extends SrcPos, interfaces.SourcePosition, Showable { def point: Int = span.point - def line: Int = if (source.content().length != 0) source.offsetToLine(point) else -1 + def line: Int = if (source.length != 0) source.offsetToLine(point) else -1 /** Extracts the lines from the underlying source file as `Array[Char]`*/ def linesSlice: Array[Char] = From ae9018a243f2129c03c368beceb4cddbc4e27cc7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 24 Aug 2020 15:38:06 +0200 Subject: [PATCH 10/21] Generate DottyPrimitives only once --- .../src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala | 2 +- compiler/src/dotty/tools/backend/jvm/GenBCode.scala | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index 407dc47733ce..15619f2530df 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -39,7 +39,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { import coreBTypes._ import BCodeBodyBuilder._ - private val primitives = new DottyPrimitives(ctx) + protected val primitives: DottyPrimitives /* * Functionality to build the body of ASM MethodNode, except for `synchronized` and `try` expressions. diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index 181e4c18f452..efc0256af177 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -50,9 +50,13 @@ class GenBCode extends Phase { myOutput } + private var myPrimitives: DottyPrimitives = null + def run(using Context): Unit = - GenBCodePipeline( - DottyBackendInterface(outputDir, superCallsMap) + if myPrimitives == null then myPrimitives = new DottyPrimitives(ctx) + new GenBCodePipeline( + DottyBackendInterface(outputDir, superCallsMap), + myPrimitives ).run(ctx.compilationUnit.tpdTree) @@ -74,7 +78,7 @@ object GenBCode { val name: String = "genBCode" } -class GenBCodePipeline(val int: DottyBackendInterface)(using Context) extends BCodeSyncAndTry { +class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrimitives)(using Context) extends BCodeSyncAndTry { import DottyBackendInterface.symExtensions private var tree: Tree = _ From 25ecbb12bdfb725e136ab0d4430253280cb5e97d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 25 Aug 2020 18:34:21 +0200 Subject: [PATCH 11/21] Make all orElse methods inline methods Make all orElse methods that take a call-by-name argument inline methods. --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 2 +- compiler/src/dotty/tools/dotc/core/Denotations.scala | 4 ++-- compiler/src/dotty/tools/dotc/core/Symbols.scala | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index a293b0e9c1bc..82702f67825d 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -174,7 +174,7 @@ object Trees { def toList: List[Tree[T]] = this :: Nil /** if this tree is the empty tree, the alternative, else this tree */ - def orElse[U >: Untyped <: T](that: => Tree[U]): Tree[U] = + inline def orElse[U >: Untyped <: T](inline that: Tree[U]): Tree[U] = if (this eq genericEmptyTree) that else this /** The number of nodes in this tree */ diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 73e98461046e..752ab3aaa955 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -237,7 +237,7 @@ object Denotations { def mapInfo(f: Type => Type)(using Context): Denotation /** If this denotation does not exist, fallback to alternative */ - final def orElse(that: => Denotation): Denotation = if (this.exists) this else that + inline def orElse(inline that: Denotation): Denotation = if (this.exists) this else that /** The set of alternative single-denotations making up this denotation */ final def alternatives: List[SingleDenotation] = altsWith(alwaysTrue) @@ -596,7 +596,7 @@ object Denotations { def mapInfo(f: Type => Type)(using Context): SingleDenotation = derivedSingleDenotation(symbol, f(info)) - def orElse(that: => SingleDenotation): SingleDenotation = if (this.exists) this else that + inline def orElse(inline that: SingleDenotation): SingleDenotation = if (this.exists) this else that def altsWith(p: Symbol => Boolean): List[SingleDenotation] = if (exists && p(symbol)) this :: Nil else Nil diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 9b0b42734381..7ba320159531 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -239,7 +239,7 @@ object Symbols { } /** This symbol, if it exists, otherwise the result of evaluating `that` */ - def orElse(that: => Symbol)(using Context): Symbol = + inline def orElse(inline that: Symbol)(using Context): Symbol = if (this.exists) this else that /** If this symbol satisfies predicate `p` this symbol, otherwise `NoSymbol` */ From a4c2768d0ce7fe5b2e086314bad9a6b4dd4efe8f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 Sep 2020 23:40:49 +0200 Subject: [PATCH 12/21] Move more mutable.HashMaps to util.HashMaps (2) --- .../dotty/tools/dotc/core/tasty/TastyUnpickler.scala | 2 +- .../dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 10 +++++----- .../src/dotty/tools/dotc/printing/Formatting.scala | 2 +- .../tools/dotc/reporting/UniqueMessagePositions.scala | 3 +-- compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala | 4 ++-- .../tools/dotc/transform/CountOuterAccesses.scala | 2 +- compiler/src/dotty/tools/dotc/util/ReadOnlyMap.scala | 9 ++++++++- 7 files changed, 19 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala index fa00db183e0b..636795cb259a 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala @@ -31,7 +31,7 @@ class TastyUnpickler(reader: TastyReader) { def this(bytes: Array[Byte]) = this(new TastyReader(bytes)) - private val sectionReader = new mutable.HashMap[String, TastyReader] + private val sectionReader = util.HashMap[String, TastyReader]() val nameAtRef: NameTable = new NameTable private def readName(): TermName = nameAtRef(readNameRef()) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index fe466c5b2836..78af63e4a801 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -59,19 +59,19 @@ class TreeUnpickler(reader: TastyReader, import tpd._ /** A map from addresses of definition entries to the symbols they define */ - private val symAtAddr = new mutable.HashMap[Addr, Symbol] + private val symAtAddr = util.HashMap[Addr, Symbol]() /** A temporary map from addresses of definition entries to the trees they define. * Used to remember trees of symbols that are created by a completion. Emptied * once the tree is inlined into a larger tree. */ - private val treeAtAddr = new mutable.HashMap[Addr, Tree] + private val treeAtAddr = util.HashMap[Addr, Tree]() /** A map from addresses of type entries to the types they define. * Currently only populated for types that might be recursively referenced * from within themselves (i.e. RecTypes, LambdaTypes). */ - private val typeAtAddr = new mutable.HashMap[Addr, Type] + private val typeAtAddr = util.HashMap[Addr, Type]() /** The root symbol denotation which are defined by the Tasty file associated with this * TreeUnpickler. Set by `enterTopLevel`. @@ -751,11 +751,11 @@ class TreeUnpickler(reader: TastyReader, * or else read definition. */ def readIndexedDef()(using Context): Tree = treeAtAddr.remove(currentAddr) match { - case Some(tree) => + case tree: Tree => assert(tree != PoisonTree, s"Cyclic reference while unpickling definition at address ${currentAddr.index} in unit ${ctx.compilationUnit}") skipTree() tree - case none => + case null => val start = currentAddr treeAtAddr(start) = PoisonTree val tree = readNewDef() diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index be25469c549d..1bca5e31c466 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -107,7 +107,7 @@ object Formatting { private type Recorded = Symbol | ParamRef | SkolemType private case class SeenKey(str: String, isType: Boolean) - private class Seen extends mutable.HashMap[SeenKey, List[Recorded]] { + private class Seen extends util.HashMap[SeenKey, List[Recorded]] { override def default(key: SeenKey) = Nil diff --git a/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala b/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala index fb4c92c12f83..a54c0f30f9fa 100644 --- a/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala +++ b/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala @@ -2,7 +2,6 @@ package dotty.tools package dotc package reporting -import scala.collection.mutable import util.SourceFile import core.Contexts._ @@ -10,7 +9,7 @@ import core.Contexts._ * are suppressed, unless they are of increasing severity. */ trait UniqueMessagePositions extends Reporter { - private val positions = new mutable.HashMap[(SourceFile, Int), Int] + private val positions = util.HashMap[(SourceFile, Int), Int]() /** Logs a position and returns true if it was already logged. * @note Two positions are considered identical for logging if they have the same point. diff --git a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala index 5eeee5dc1dd3..7af896582e43 100644 --- a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala +++ b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala @@ -10,7 +10,7 @@ import dotty.tools.dotc.reporting.Reporter /** Handles rewriting of Scala2 files to Dotty */ object Rewrites { - private class PatchedFiles extends mutable.HashMap[SourceFile, Patches] + private class PatchedFiles extends util.HashMap[SourceFile, Patches] private case class Patch(span: Span, replacement: String) { def delta = replacement.length - (span.end - span.start) @@ -83,7 +83,7 @@ object Rewrites { /** If -rewrite is set, apply all patches and overwrite patched source files. */ def writeBack()(using Context): Unit = - for (rewrites <- ctx.settings.rewrite.value; source <- rewrites.patched.keys) { + for (rewrites <- ctx.settings.rewrite.value; source <- rewrites.patched.keysIterator) { report.echo(s"[patched file ${source.file.path}]") rewrites.patched(source).writeBack() } diff --git a/compiler/src/dotty/tools/dotc/transform/CountOuterAccesses.scala b/compiler/src/dotty/tools/dotc/transform/CountOuterAccesses.scala index 27a7907b266e..73f2fe42c5a1 100644 --- a/compiler/src/dotty/tools/dotc/transform/CountOuterAccesses.scala +++ b/compiler/src/dotty/tools/dotc/transform/CountOuterAccesses.scala @@ -42,7 +42,7 @@ class CountOuterAccesses extends MiniPhase: // LambdaLift can create outer paths. These need to be known in this phase. /** The number of times an outer accessor that might be dropped is accessed */ - val outerAccessCount = new mutable.HashMap[Symbol, Int] { + val outerAccessCount = new util.HashMap[Symbol, Int] { override def default(s: Symbol): Int = 0 } diff --git a/compiler/src/dotty/tools/dotc/util/ReadOnlyMap.scala b/compiler/src/dotty/tools/dotc/util/ReadOnlyMap.scala index 020303c18bc2..7eb3d8b4fea3 100644 --- a/compiler/src/dotty/tools/dotc/util/ReadOnlyMap.scala +++ b/compiler/src/dotty/tools/dotc/util/ReadOnlyMap.scala @@ -1,5 +1,6 @@ package dotty.tools package dotc.util +import collection.mutable.ListBuffer /** A class for the reading part of mutable or immutable maps. */ @@ -26,9 +27,12 @@ abstract class ReadOnlyMap[Key, Value]: def contains(key: Key): Boolean = lookup(key) != null def apply(key: Key): Value = lookup(key) match - case null => throw new NoSuchElementException(s"$key") + case null => default(key) case v => v.uncheckedNN + protected def default(key: Key): Value = + throw new NoSuchElementException(s"$key") + def toArray: Array[(Key, Value)] = val result = new Array[(Key, Value)](size) var idx = 0 @@ -37,5 +41,8 @@ abstract class ReadOnlyMap[Key, Value]: idx += 1 result + def toList: List[(Key, Value)] = + (new ListBuffer[(Key, Value)]() ++= iterator).toList + def toSeq: Seq[(Key, Value)] = toArray.toSeq From bb8b340c6477151c66d8f7ed15b8f02a45acb14c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Sep 2020 19:30:25 +0200 Subject: [PATCH 13/21] Reduce context creations (1) --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 6 +++++- .../dotty/tools/dotc/transform/Instrumentation.scala | 10 +++++----- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 6 ++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index b2258dee613d..179b40a18bb4 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -674,7 +674,7 @@ object Contexts { final def retractMode(mode: Mode): c.type = c.setMode(c.mode &~ mode) } - private def exploreCtx(using Context): Context = + private def exploreCtx(using Context): FreshContext = util.Stats.record("explore") val base = ctx.base import base._ @@ -701,6 +701,10 @@ object Contexts { val ectx = exploreCtx try op(using ectx) finally wrapUpExplore(ectx) + inline def exploreInFreshCtx[T](inline op: FreshContext ?=> T)(using Context): T = + val ectx = exploreCtx + try op(using ectx) finally wrapUpExplore(ectx) + private def changeOwnerCtx(owner: Symbol)(using Context): Context = val base = ctx.base import base._ diff --git a/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala b/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala index b4d2c541f2c5..5cbf355ad60c 100644 --- a/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala +++ b/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala @@ -29,17 +29,17 @@ class Instrumentation extends MiniPhase { thisPhase => ctx.settings.Yinstrument.value private val collectionNamesOfInterest = List( - "map", "flatMap", "filter", "filterNot", "withFilter", "collect", "flatten", "foldLeft", "foldRight", "take", - "reverse", "zip", "++", ":::", ":+", "distinct", "dropRight", "takeRight", "groupBy", "groupMap", "init", "inits", - "interect", "mkString", "partition", "reverse_:::", "scanLeft", "scanRight", - "sortBy", "sortWith", "sorted", "span", "splitAt", "takeWhile", "transpose", "unzip", "unzip3", + "map", "flatMap", "filter", "filterNot", "withFilter", "flatten", "take", + "reverse", "zip", "++", ":::", ":+", "distinct", "dropRight", "takeRight", "groupBy", "groupMap", + "interect", "mkString", "partition", "reverse_:::", + "sortBy", "sortWith", "sorted", "splitAt", "takeWhile", "transpose", "unzip", "unzip3", "updated", "zipAll", "zipWithIndex", "mapConserve", "mapconserve", "filterConserve", "zipWithConserve", "mapWithIndexConserve" ) private val namesOfInterest = collectionNamesOfInterest ++ List( "::", "+=", "toString", "newArray", "box", "toCharArray", "termName", "typeName", - "slice", "staticRef", "requiredClass") + "toList", "fresh") private var namesToRecord: Set[Name] = _ private var collectionNamesToRecord: Set[Name] = _ diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 07791b9d046b..83849c8dcb47 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -239,10 +239,12 @@ object Implicits: if refs.isEmpty && (!considerExtension || companionRefs.isEmpty) then Nil else - val nestedCtx = ctx.fresh.addMode(Mode.TypevarsMissContext) val candidates = new mutable.ListBuffer[Candidate] def tryCandidate(extensionOnly: Boolean)(ref: ImplicitRef) = - var ckind = explore(candidateKind(ref.underlyingRef))(using nestedCtx) + var ckind = exploreInFreshCtx { (using ctx: FreshContext) => + ctx.setMode(ctx.mode | Mode.TypevarsMissContext) + candidateKind(ref.underlyingRef) + } if extensionOnly then ckind &= Candidate.Extension if ckind != Candidate.None then candidates += Candidate(ref, ckind, level) From 4486a1ae13854227024d4aa3f71d92be4ccf07d8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Sep 2020 19:39:49 +0200 Subject: [PATCH 14/21] Avoid creating a fresh TyperState in isFullyDefined --- compiler/src/dotty/tools/dotc/core/TyperState.scala | 7 +++++-- .../src/dotty/tools/dotc/typer/Inferencing.scala | 13 +++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 8b4b6a476d1b..48b067c86766 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -113,9 +113,12 @@ class TyperState() { * isApplicableSafe but also for (e.g. erased-lubs.scala) as well as * many parts of dotty itself. */ - def commit()(using Context): Unit = { - Stats.record("typerState.commit") + def commit()(using Context): Unit = assert(isCommittable) + uncheckedCommit() + + def uncheckedCommit()(using Context): Unit = { + Stats.record("typerState.commit") val targetState = ctx.typerState if constraint ne targetState.constraint then Stats.record("typerState.commit.new constraint") diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 962c52fed35a..ab82a480562c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -31,12 +31,13 @@ object Inferencing { * but only if the overall result of `isFullyDefined` is `true`. * Variables that are successfully minimized do not count as uninstantiated. */ - def isFullyDefined(tp: Type, force: ForceDegree.Value)(using Context): Boolean = { - val nestedCtx = ctx.fresh.setNewTyperState() - val result = new IsFullyDefinedAccumulator(force)(using nestedCtx).process(tp) - if (result) nestedCtx.typerState.commit() - result - } + def isFullyDefined(tp: Type, force: ForceDegree.Value)(using Context): Boolean = + val current = ctx + explore { + val result = IsFullyDefinedAccumulator(force).process(tp) + if result then ctx.typerState.uncheckedCommit()(using current) + result + } /** The fully defined type, where all type variables are forced. * Throws an error if type contains wildcards. From 07aefa94f697a2a0c50a64985b990f765afa0eca Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Sep 2020 20:40:57 +0200 Subject: [PATCH 15/21] Fix test --- tests/pos-with-compiler/benchSets.scala | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/tests/pos-with-compiler/benchSets.scala b/tests/pos-with-compiler/benchSets.scala index 41088de7f30b..3e1492e47980 100644 --- a/tests/pos-with-compiler/benchSets.scala +++ b/tests/pos-with-compiler/benchSets.scala @@ -98,23 +98,6 @@ def testScalaSet = iter += 1 count -def testLinearSet = - var set = dotty.tools.dotc.util.LinearIdentitySet.empty[Elem] - var count = 0 - var iter = 0 - while iter < ItersPerRun do - var i = 0 - while i < elems.length do - val e = elems(i) - if i % MissFactor == 0 then - set += e - i += 1 - while i > 0 do - i -= 1 - if set.contains(elems(i)) then - count += 1 - iter += 1 - count val expected = (elems.size / MissFactor) * ItersPerRun @@ -135,9 +118,7 @@ def profile(name: String, op: => Int) = profile("scala.collection.HashMap ", testScalaMap) profile("scala.collection.HashSet ", testScalaSet) profile("dotty.tools.dotc.HashSet ", testDottySet) - profile("dotty.tools.dotc.LinearSet", testLinearSet) - profile("dotty.tools.dotc.LinearSet", testLinearSet) profile("dotty.tools.dotc.HashSet ", testDottySet) profile("scala.collection.HashSet ", testScalaSet) profile("scala.collection.HashMap ", testScalaMap) From 9f42ffa458eb7b6ed0fa7e0336b6b6a039dac543 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Sep 2020 14:44:05 +0200 Subject: [PATCH 16/21] Change Bench to give better timings under -YdetailedStats Run stats only at last run. This allows one to warm up and JOT compile code before measurements start. --- compiler/src/dotty/tools/dotc/Bench.scala | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Bench.scala b/compiler/src/dotty/tools/dotc/Bench.scala index 3d264141eb71..e7a61fd558d7 100644 --- a/compiler/src/dotty/tools/dotc/Bench.scala +++ b/compiler/src/dotty/tools/dotc/Bench.scala @@ -22,9 +22,12 @@ object Bench extends Driver: override def doCompile(compiler: Compiler, fileNames: List[String])(using Context): Reporter = times = new Array[Int](numRuns) var reporter: Reporter = emptyReporter + val stats = ctx.settings.YdetailedStats.value for i <- 0 until numRuns do val start = System.nanoTime() - reporter = super.doCompile(compiler, fileNames) + reporter = inContext(ctx.fresh.setSetting(ctx.settings.YdetailedStats, stats && i == numRuns - 1)) { + super.doCompile(compiler, fileNames) + } times(i) = ((System.nanoTime - start) / 1000000).toInt println(s"time elapsed: ${times(i)}ms") if ctx.settings.Xprompt.value then @@ -33,11 +36,10 @@ object Bench extends Driver: println() reporter - def extractNumArg(args: Array[String], name: String, default: Int = 1): (Int, Array[String]) = { - val pos = args indexOf name - if (pos < 0) (default, args) - else (args(pos + 1).toInt, (args take pos) ++ (args drop (pos + 2))) - } + def extractNumArg(args: Array[String], name: String, default: Int = 1): (Int, Array[String]) = + val pos = args.indexOf(name) + if pos < 0 then (default, args) + else (args(pos + 1).toInt, args.take(pos) ++ args.drop(pos + 2)) def reportTimes() = val best = times.sorted From 31a48745839a39b237500cb6cb431b8024136379 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Sep 2020 10:40:11 +0200 Subject: [PATCH 17/21] Track some implicit timings If Stats is enabled, track total time spent in - implicit search overall - searching implicits in context / in type scope - typedImplicit --- .../src/dotty/tools/dotc/typer/Implicits.scala | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 83849c8dcb47..89371bc540a3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -976,6 +976,7 @@ trait Implicits: */ def inferImplicit(pt: Type, argument: Tree, span: Span)(using Context): SearchResult = trace(s"search implicit ${pt.show}, arg = ${argument.show}: ${argument.tpe.show}", implicits, show = true) { + Stats.trackTime("inferImplicit ms") { record("inferImplicit") assert(ctx.phase.allowsImplicitSearch, if (argument.isEmpty) i"missing implicit parameter of type $pt after typer" @@ -1015,12 +1016,12 @@ trait Implicits: } // If we are at the outermost implicit search then emit the implicit dictionary, if any. ctx.searchHistory.emitDictionary(span, result) - } + }} /** Try to typecheck an implicit reference */ def typedImplicit(cand: Candidate, pt: Type, argument: Tree, span: Span)(using Context): SearchResult = trace(i"typed implicit ${cand.ref}, pt = $pt, implicitsEnabled == ${ctx.mode is ImplicitsEnabled}", implicits, show = true) { if ctx.run.isCancelled then NoMatchingImplicitsFailure - else + else Stats.trackTime("typed implicit ms") { record("typedImplicit") val ref = cand.ref val generated: Tree = tpd.ref(ref).withSpan(span.startPos) @@ -1083,7 +1084,7 @@ trait Implicits: if (cand.isExtension) Applications.ExtMethodApply(adapted) else adapted SearchSuccess(returned, ref, cand.level)(ctx.typerState, ctx.gadt) - } + }} /** An implicit search; parameters as in `inferImplicit` */ class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span)(using Context): @@ -1299,8 +1300,14 @@ trait Implicits: private def searchImplicit(contextual: Boolean): SearchResult = val eligible = - if contextual then ctx.implicits.eligible(wildProto) - else implicitScope(wildProto).eligible + if contextual then + Stats.trackTime("contextual eligible ms") { + ctx.implicits.eligible(wildProto) + } + else + Stats.trackTime("implicit scope eligible ms") { + implicitScope(wildProto).eligible + } searchImplicit(eligible, contextual) match case result: SearchSuccess => result From 9982d19cc22c95a0ac0ac91029d1f3e968a5ddd8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Sep 2020 10:40:48 +0200 Subject: [PATCH 18/21] Track time spent in resolveOverloaded --- .../dotty/tools/dotc/typer/Applications.scala | 3 ++- .../src/dotty/tools/dotc/typer/Typer.scala | 23 +++++++++---------- .../src/dotty/tools/dotc/util/Stats.scala | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 1376199c9162..65da39e326bf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1647,7 +1647,7 @@ trait Applications extends Compatibility { * Two trials: First, without implicits or SAM conversions enabled. Then, * if the first finds no eligible candidates, with implicits and SAM conversions enabled. */ - def resolveOverloaded(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = + def resolveOverloaded(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = util.Stats.trackTime("resolveOver ms") { record("resolveOverloaded") /** Is `alt` a method or polytype whose result type after the first value parameter @@ -1745,6 +1745,7 @@ trait Applications extends Compatibility { resolve(expanded).map(retract) } else resolve(alts) + } end resolveOverloaded /** This private version of `resolveOverloaded` does the bulk of the work of diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 463f25382b6b..88cc4917a880 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -29,13 +29,12 @@ import EtaExpansion.etaExpand import TypeComparer.CompareResult import util.Spans._ import util.common._ -import util.{Property, SimpleIdentityMap, SrcPos} +import util.{Property, SimpleIdentityMap, SrcPos, Stats} import Applications.{ExtMethodApply, IntegratedTypeArgs, productSelectorTypes, wrapDefs} import collection.mutable import annotation.tailrec import Implicits._ -import util.Stats.record import config.Printers.{gadts, typr, debug} import config.Feature._ import config.SourceVersion._ @@ -434,7 +433,7 @@ class Typer extends Namer * (3) Change pattern Idents id (but not wildcards) to id @ _ */ def typedIdent(tree: untpd.Ident, pt: Type)(using Context): Tree = - record("typedIdent") + Stats.record("typedIdent") val name = tree.name def kind = if (name.isTermName) "" else "type " typr.println(s"typed ident $kind$name in ${ctx.owner}") @@ -551,7 +550,7 @@ class Typer extends Namer } def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { - record("typedSelect") + Stats.record("typedSelect") def typeSelectOnTerm(using Context): Tree = typedSelect(tree, pt, typedExpr(tree.qualifier, selectionProto(tree.name, pt, this))) @@ -582,7 +581,7 @@ class Typer extends Namer } def typedThis(tree: untpd.This)(using Context): Tree = { - record("typedThis") + Stats.record("typedThis") assignType(tree) } @@ -602,7 +601,7 @@ class Typer extends Namer def typedNumber(tree: untpd.Number, pt: Type)(using Context): Tree = { import scala.util.FromDigits._ import untpd.NumberKind._ - record("typedNumber") + Stats.record("typedNumber") val digits = tree.digits val target = pt.dealias def lit(value: Any) = Literal(Constant(value)).withSpan(tree.span) @@ -2416,7 +2415,7 @@ class Typer extends Namer * at the present time */ def typedUnadapted(initTree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = { - record("typedUnadapted") + Stats.record("typedUnadapted") val xtree = expanded(initTree) xtree.removeAttachment(TypedAhead) match { case Some(ttree) => ttree @@ -2578,8 +2577,8 @@ class Typer extends Namer /** Typecheck and adapt tree, returning a typed tree. Parameters as for `typedUnadapted` */ def typed(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = trace(i"typing $tree, pt = $pt", typr, show = true) { - record(s"typed $getClass") - record("typed total") + Stats.record(s"typed $getClass") + Stats.record("typed total") if ctx.phase.isTyper then assertPositioned(tree) if tree.source != ctx.source && tree.source.exists then @@ -2715,11 +2714,11 @@ class Typer extends Namer val nestedCtx = ctx.fresh.setNewTyperState() val result = op(using nestedCtx) if (nestedCtx.reporter.hasErrors && !nestedCtx.reporter.hasStickyErrors) { - record("tryEither.fallBack") + Stats.record("tryEither.fallBack") fallBack(result, nestedCtx.typerState) } else { - record("tryEither.commit") + Stats.record("tryEither.commit") nestedCtx.typerState.commit() result } @@ -2916,7 +2915,7 @@ class Typer extends Namer def adapt(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean = true)(using Context): Tree = try trace(i"adapting $tree to $pt ${if (tryGadtHealing) "" else "(tryGadtHealing=false)" }\n", typr, show = true) { - record("adapt") + Stats.record("adapt") adapt1(tree, pt, locked, tryGadtHealing) } catch case ex: TypeError => errorTree(tree, ex, tree.srcPos.focus) diff --git a/compiler/src/dotty/tools/dotc/util/Stats.scala b/compiler/src/dotty/tools/dotc/util/Stats.scala index 684083fa77ba..a6c258180807 100644 --- a/compiler/src/dotty/tools/dotc/util/Stats.scala +++ b/compiler/src/dotty/tools/dotc/util/Stats.scala @@ -9,7 +9,7 @@ import collection.mutable @sharable object Stats { - final val enabled = false + inline val enabled = false var monitored: Boolean = false From 5db64c3525cb3277d7f50221ac2c4b2ec5a8191f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Sep 2020 11:18:33 +0200 Subject: [PATCH 19/21] Fix hashcodes of inner case classes in NameKinds See #9748 for why this is necessary --- compiler/src/dotty/tools/dotc/core/NameKinds.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 6d3bd93f4a6d..28791b168aeb 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -124,6 +124,7 @@ object NameKinds { case class QualInfo(name: SimpleName) extends Info with QualifiedInfo { override def map(f: SimpleName => SimpleName): NameInfo = new QualInfo(f(name)) override def toString: String = s"$infoString $name" + override def hashCode = scala.runtime.ScalaRunTime._hashCode(this) * 31 + kind.hashCode } def apply(qual: TermName, name: SimpleName): TermName = @@ -173,6 +174,7 @@ object NameKinds { type ThisInfo = NumberedInfo case class NumberedInfo(val num: Int) extends Info with NameKinds.NumberedInfo { override def toString: String = s"$infoString $num" + override def hashCode = scala.runtime.ScalaRunTime._hashCode(this) * 31 + kind.hashCode } def apply(qual: TermName, num: Int): TermName = qual.derived(new NumberedInfo(num)) @@ -371,6 +373,7 @@ object NameKinds { case class SignedInfo(sig: Signature) extends Info { assert(sig ne Signature.NotAMethod) override def toString: String = s"$infoString $sig" + override def hashCode = scala.runtime.ScalaRunTime._hashCode(this) * 31 + kind.hashCode } type ThisInfo = SignedInfo From 682ed7071a7c44345f2c29ddd425b41a8a47dc8f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Sep 2020 11:21:15 +0200 Subject: [PATCH 20/21] Fix growTable function for HashMap When tranisitioning from dense to hashing, we grow the table more than usual since otherwise we'd have to grow it again at the very next addEntry. The condition for this was wrong for HashMaps. --- compiler/src/dotty/tools/dotc/util/GenericHashMap.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/util/GenericHashMap.scala b/compiler/src/dotty/tools/dotc/util/GenericHashMap.scala index 134a94e8b888..8a806bd42b7b 100644 --- a/compiler/src/dotty/tools/dotc/util/GenericHashMap.scala +++ b/compiler/src/dotty/tools/dotc/util/GenericHashMap.scala @@ -156,7 +156,7 @@ abstract class GenericHashMap[Key, Value] protected def growTable(): Unit = val oldTable = table val newLength = - if oldTable.length == DenseLimit then DenseLimit * 2 * roundToPower(capacityMultiple) + if table.length == DenseLimit * 2 then table.length * roundToPower(capacityMultiple) else table.length allocate(newLength) copyFrom(oldTable) From dd006212c4dd0146c86e77c076edab427d4ea63b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Sep 2020 16:08:55 +0200 Subject: [PATCH 21/21] Fix HashMap and HashSet remove criterion Make criterion when to fill a hole in a HashMap or HashSet remove more robust. The old criterion relied on fill factor always being less than 0.5. The new criterion works for arbitrary fill factors. --- .../tools/dotc/util/GenericHashMap.scala | 6 +- .../src/dotty/tools/dotc/util/HashSet.scala | 6 +- tests/run-with-compiler/settest.scala | 71 +++++++++++++++++++ 3 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 tests/run-with-compiler/settest.scala diff --git a/compiler/src/dotty/tools/dotc/util/GenericHashMap.scala b/compiler/src/dotty/tools/dotc/util/GenericHashMap.scala index 8a806bd42b7b..f2a3f012f76c 100644 --- a/compiler/src/dotty/tools/dotc/util/GenericHashMap.scala +++ b/compiler/src/dotty/tools/dotc/util/GenericHashMap.scala @@ -111,9 +111,11 @@ abstract class GenericHashMap[Key, Value] k = keyAt(idx) k != null do + val eidx = index(hash(k)) if isDense - || index(hole - index(hash(k))) < limit * 2 - // hash(k) is then logically at or before hole; can be moved forward to fill hole + || index(eidx - (hole + 2)) > index(idx - (hole + 2)) + // entry `e` at `idx` can move unless `index(hash(e))` is in + // the (ring-)interval [hole + 2 .. idx] then setKey(hole, k) setValue(hole, valueAt(idx)) diff --git a/compiler/src/dotty/tools/dotc/util/HashSet.scala b/compiler/src/dotty/tools/dotc/util/HashSet.scala index 8d73eada970f..bd764c523d83 100644 --- a/compiler/src/dotty/tools/dotc/util/HashSet.scala +++ b/compiler/src/dotty/tools/dotc/util/HashSet.scala @@ -109,9 +109,11 @@ class HashSet[T](initialCapacity: Int = 8, capacityMultiple: Int = 2) extends Mu e = entryAt(idx) e != null do + val eidx = index(hash(e)) if isDense - || index(hole - index(hash(e))) < limit - // hash(k) is then logically at or before hole; can be moved forward to fill hole + || index(eidx - (hole + 1)) > index(idx - (hole + 1)) + // entry `e` at `idx` can move unless `index(hash(e))` is in + // the (ring-)interval [hole + 1 .. idx] then setEntry(hole, e) hole = idx diff --git a/tests/run-with-compiler/settest.scala b/tests/run-with-compiler/settest.scala new file mode 100644 index 000000000000..2d930ca3fdf9 --- /dev/null +++ b/tests/run-with-compiler/settest.scala @@ -0,0 +1,71 @@ +trait Generator[+T]: + self => + def generate: T + def map[S](f: T => S) = new Generator[S]: + def generate: S = f(self.generate) + def flatMap[S](f: T => Generator[S]) = new Generator[S]: + def generate: S = f(self.generate).generate + +object Generator: + val NumLimit = 300 + val Iterations = 10000 + + given integers as Generator[Int]: + val rand = new java.util.Random + def generate = rand.nextInt() + + given booleans as Generator[Boolean] = + integers.map(x => x > 0) + + def range(end: Int): Generator[Int] = + integers.map(x => (x % end).abs) + + enum Op: + case Lookup, Update, Remove + export Op._ + + given ops as Generator[Op] = + range(10).map { + case 0 | 1 | 2 | 3 => Lookup + case 4 | 5 | 6 | 7 => Update + case 8 | 9 => Remove + } + + val nums: Generator[Integer] = range(NumLimit).map(Integer(_)) + +@main def Test = + import Generator._ + + val set1 = dotty.tools.dotc.util.HashSet[Int]() + val set2 = scala.collection.mutable.HashSet[Int]() + + def checkSame() = + assert(set1.size == set2.size) + for e <- set1.iterator do + assert(set2.contains(e)) + for e <- set2.iterator do + assert(set1.contains(e)) + + def lookupTest(num: Integer) = + val res1 = set1.contains(num) + val res2 = set2.contains(num) + assert(res1 == res2) + + def updateTest(num: Integer) = + lookupTest(num) + set1 += num + set2 += num + checkSame() + + def removeTest(num: Integer) = + //println(s"test remove $num") + set1 -= num + set2 -= num + checkSame() + + for i <- 0 until Iterations do + val num = nums.generate + Generator.ops.generate match + case Lookup => lookupTest(num) + case Update => updateTest(num) + case Remove => removeTest(num)