From 25b5b885fcaf4f79e71c050a2049510b6e16bda9 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Thu, 4 Apr 2024 10:09:17 +0200 Subject: [PATCH] Use lock-free fifo cache for `parsedClasses` in backend Use a lock-free fifo cache for `parsedClasses` in the backend when `-Ybackend-parallelism` is enabled. --- .../backend/jvm/opt/ByteCodeRepository.scala | 3 +- .../tools/nsc/backend/jvm/opt/FifoCache.scala | 56 +++++++++++++++++++ .../tools/nsc/backend/jvm/opt/LruMap.scala | 33 ----------- .../neg/case-collision-multifile/one.scala | 2 +- .../nsc/backend/jvm/opt/CallGraphTest.scala | 5 +- 5 files changed, 62 insertions(+), 37 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/backend/jvm/opt/FifoCache.scala delete mode 100644 src/compiler/scala/tools/nsc/backend/jvm/opt/LruMap.scala diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala index e21c3c19466f..3d5165a73da8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala @@ -56,7 +56,8 @@ abstract class ByteCodeRepository extends PerRunInit { * Note - although this is typed a mutable.Map, individual simple get and put operations are threadsafe as the * underlying data structure is synchronized. */ - val parsedClasses: mutable.Map[InternalName, Either[ClassNotFound, ClassNode]] = recordPerRunCache(LruMap[InternalName, Either[ClassNotFound, ClassNode]](maxCacheSize, threadsafe = true)) + val parsedClasses: mutable.Map[InternalName, Either[ClassNotFound, ClassNode]] = + recordPerRunCache(FifoCache[InternalName, Either[ClassNotFound, ClassNode]](maxCacheSize, threadsafe = true)) /** * Contains the internal names of all classes that are defined in Java source files of the current diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/FifoCache.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/FifoCache.scala new file mode 100644 index 000000000000..9e4a327f5d5f --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/FifoCache.scala @@ -0,0 +1,56 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.tools.nsc.backend.jvm.opt + +import java.util.concurrent.{ConcurrentHashMap, ConcurrentLinkedQueue} +import java.util.{LinkedHashMap, Map => JMap} +import scala.collection.mutable +import scala.jdk.CollectionConverters._ + +object FifoCache { + def apply[K,V](maxSize: Int, threadsafe: Boolean): mutable.Map[K,V] = { + require(maxSize > 0) + if (threadsafe) new ConcFifoCache(maxSize) else new FifoCache[K, V](maxSize).asScala + } + + private class FifoCache[K, V](maxSize: Int) extends LinkedHashMap[K,V] { + override def removeEldestEntry(eldest: JMap.Entry[K, V]): Boolean = { + size() > maxSize + } + } + + private class ConcFifoCache[K, V](maxSize: Int) extends mutable.Map[K,V] { + private val cache: ConcurrentHashMap[K, V] = new ConcurrentHashMap() + private val queue: ConcurrentLinkedQueue[K] = new ConcurrentLinkedQueue() + + def get(key: K): Option[V] = Option(cache.get(key)) + + def subtractOne(key: K): this.type = { + cache.remove(key) + queue.remove(key) + this + } + + def addOne(elem: (K, V)): this.type = { + while (cache.size() >= maxSize) { + val oldest = queue.poll() + if (oldest != null) cache.remove(oldest) + } + queue.add(elem._1) + cache.put(elem._1, elem._2) + this + } + + def iterator: Iterator[(K, V)] = cache.entrySet.iterator.asScala.map(e => (e.getKey, e.getValue)) + } +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/LruMap.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/LruMap.scala deleted file mode 100644 index d73e3a6c1a4a..000000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LruMap.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Scala (https://www.scala-lang.org) - * - * Copyright EPFL and Lightbend, Inc. - * - * Licensed under Apache License 2.0 - * (http://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package scala.tools.nsc.backend.jvm.opt - -import scala.collection.mutable.Map -import scala.jdk.CollectionConverters._ -import java.util.{LinkedHashMap, Collections, Map => JMap} - -object LruMap{ - def apply[K,V](maxSize: Int, threadsafe: Boolean): Map[K,V] = { - require(maxSize > 0) - val basic = new LruMapImpl[K,V](maxSize) - val threaded = if (threadsafe) Collections.synchronizedMap(basic) else basic - - threaded.asScala - } - - private class LruMapImpl[K,V](maxSize: Int) extends LinkedHashMap[K,V] { - override def removeEldestEntry(eldest: JMap.Entry[K, V]): Boolean = { - size() > maxSize - } - } -} diff --git a/test/files/neg/case-collision-multifile/one.scala b/test/files/neg/case-collision-multifile/one.scala index 1ae99705908b..5da343dae653 100644 --- a/test/files/neg/case-collision-multifile/one.scala +++ b/test/files/neg/case-collision-multifile/one.scala @@ -1,2 +1,2 @@ -// scalac: -Werror +//> using options -Werror -Ybackend-parallelism 1 class HotDog diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala index 55eddf84341c..ed0019589223 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -12,6 +12,7 @@ import scala.reflect.internal.util.JavaClearable import scala.tools.asm.tree._ import scala.tools.nsc.backend.jvm.BackendReporting._ import scala.tools.nsc.reporters.StoreReporter +import scala.tools.testkit.ASMConverters.convertMethod import scala.tools.testkit.BytecodeTesting import scala.tools.testkit.BytecodeTesting._ @@ -42,9 +43,9 @@ class CallGraphTest extends BytecodeTesting { val callsite = callGraph.callsites(callsiteMethod)(call) try { assert(callsite.callsiteInstruction == call) - assert(callsite.callsiteMethod == callsiteMethod) + assert(convertMethod(callsite.callsiteMethod) == convertMethod(callsiteMethod)) val callee = callsite.callee.get - assert(callee.callee == target) + assert(convertMethod(callee.callee) == convertMethod(target)) assert(callee.calleeDeclarationClass == calleeDeclClass) assertEquals("safeToInline", safeToInline, callee.safeToInline) assert(callee.annotatedInline == atInline)