Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to use scala.collection.concurrent.TrieMap #3149

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Ported from Scala 2.13.10
package scala.collection.concurrent

abstract class BasicNode {
def string(lev: Int): String
}
27 changes: 27 additions & 0 deletions auxlib/src/main/scala/scala/collection/concurrent/CNodeBase.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package scala.collection.concurrent

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater

import scala.scalanative.annotation.alwaysinline
import scala.scalanative.runtime.Intrinsics.classFieldRawPtr
import scala.scalanative.runtime.fromRawPtr

private[concurrent] abstract class CNodeBase[K <: AnyRef, V <: AnyRef]
extends MainNode[K, V] {
@volatile var csize: Int = -1

final val updater: AtomicIntegerFieldUpdater[CNodeBase[_, _]] =
new IntrinsicAtomicIntegerFieldUpdater(obj =>
fromRawPtr(classFieldRawPtr(obj, "csize"))
)

@alwaysinline
def CAS_SIZE(oldval: Int, nval: Int) =
updater.compareAndSet(this, oldval, nval)

@alwaysinline
def WRITE_SIZE(nval: Int): Unit = updater.set(this, nval)

@alwaysinline
def READ_SIZE: Int = updater.get(this)
}
5 changes: 5 additions & 0 deletions auxlib/src/main/scala/scala/collection/concurrent/Gen.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Ported from Scala 2.13.10

package scala.collection.concurrent

private[concurrent] final class Gen {}
28 changes: 28 additions & 0 deletions auxlib/src/main/scala/scala/collection/concurrent/INodeBase.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Ported from Scala 2.13.10

package scala.collection.concurrent

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater

import scala.scalanative.runtime.Intrinsics.classFieldRawPtr
import scala.scalanative.runtime.fromRawPtr

object INodeBase {
final val updater
: AtomicReferenceFieldUpdater[INodeBase[_, _], MainNode[_, _]] =
new IntrinsicAtomicReferenceFieldUpdater(obj =>
fromRawPtr(classFieldRawPtr(obj, "mainnode"))
)

final val RESTART = new Object {}
final val NO_SUCH_ELEMENT_SENTINEL = new Object {}
}

private[concurrent] abstract class INodeBase[K <: AnyRef, V <: AnyRef](
generation: Gen
) extends BasicNode {
@volatile var mainnode: MainNode[K, V] = _
final var gen: Gen = generation

def prev(): BasicNode = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package scala.collection.concurrent

import java.util.concurrent.atomic.{
AtomicIntegerFieldUpdater,
AtomicReferenceFieldUpdater
}

import scala.scalanative.runtime.Intrinsics.classFieldRawPtr
import scala.scalanative.runtime.{RawPtr, fromRawPtr}
import scala.scalanative.annotation.alwaysinline
import scala.scalanative.libc.atomic.{CAtomicRef, CAtomicInt, memory_order}
import scala.scalanative.unsafe.Ptr

private[concurrent] class IntrinsicAtomicReferenceFieldUpdater[
T <: AnyRef,
V <: AnyRef
](@alwaysinline selector: T => Ptr[V])
extends AtomicReferenceFieldUpdater[T, V]() {
@alwaysinline private def atomicRef(insideObj: T) =
new CAtomicRef[V](selector(insideObj))

@alwaysinline def compareAndSet(obj: T, expect: V, update: V): Boolean =
atomicRef(obj).compareExchangeStrong(expect, update)

@alwaysinline def weakCompareAndSet(obj: T, expect: V, update: V): Boolean =
atomicRef(obj).compareExchangeWeak(expect, update)

@alwaysinline def set(obj: T, newIntalue: V): Unit =
atomicRef(obj).store(newIntalue)

@alwaysinline def lazySet(obj: T, newIntalue: V): Unit =
atomicRef(obj).store(newIntalue, memory_order.memory_order_release)

@alwaysinline def get(obj: T): V = atomicRef(obj).load()
}

class IntrinsicAtomicIntegerFieldUpdater[T <: AnyRef](
@alwaysinline selector: T => Ptr[Int]
) extends AtomicIntegerFieldUpdater[T]() {
@alwaysinline private def atomicRef(insideObj: T) = new CAtomicInt(
selector(insideObj)
)

@alwaysinline def compareAndSet(obj: T, expect: Int, update: Int): Boolean =
atomicRef(obj).compareExchangeStrong(expect, update)

@alwaysinline def weakCompareAndSet(
obj: T,
expect: Int,
update: Int
): Boolean =
atomicRef(obj).compareExchangeWeak(expect, update)

@alwaysinline def set(obj: T, newIntalue: Int): Unit =
atomicRef(obj).store(newIntalue)

@alwaysinline def lazySet(obj: T, newIntalue: Int): Unit =
atomicRef(obj).store(newIntalue, memory_order.memory_order_release)

@alwaysinline def get(obj: T): Int = atomicRef(obj).load()
}
38 changes: 38 additions & 0 deletions auxlib/src/main/scala/scala/collection/concurrent/MainNode.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package scala.collection.concurrent

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater

import scala.scalanative.annotation.alwaysinline
import scala.scalanative.runtime.Intrinsics.classFieldRawPtr
import scala.scalanative.runtime.fromRawPtr

object MainNode {
final val updater
: AtomicReferenceFieldUpdater[MainNode[_, _], MainNode[_, _]] =
new IntrinsicAtomicReferenceFieldUpdater(obj =>
fromRawPtr(classFieldRawPtr(obj, "prev"))
)
}

private[concurrent] abstract class MainNode[K <: AnyRef, V <: AnyRef]
extends BasicNode {
import MainNode.updater

@volatile var prev: MainNode[K, V] = _

def cachedSize(ct: Object): Int

// standard contract
def knownSize(): Int

@alwaysinline
def CAS_PREV(oldval: MainNode[K, V], nval: MainNode[K, V]) =
updater.compareAndSet(this, oldval, nval)

@alwaysinline
def WRITE_PREV(nval: MainNode[K, V]): Unit = updater.set(this, nval)

@deprecated
@alwaysinline def READ_PREV(): MainNode[K, V] =
updater.get(this).asInstanceOf[MainNode[K, V]]
}
9 changes: 4 additions & 5 deletions javalib/src/main/scala/java/lang/Throwables.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import scalanative.unsafe._
import scalanative.unsigned._
import scalanative.runtime.unwind
import scala.scalanative.meta.LinktimeInfo
// TODO: Replace with j.u.c.ConcurrentHashMap when implemented to remove scalalib dependency
import scala.collection.concurrent.TrieMap

private[lang] object StackTrace {
// TODO: Replace s.c.c.TrieMap/j.u.c.ConcurrentHashMap
private val cache = ThreadLocal.withInitial { () =>
collection.mutable.HashMap.empty[CUnsignedLong, StackTraceElement]
}
private val cache = TrieMap.empty[CUnsignedLong, StackTraceElement]

private def makeStackTraceElement(
cursor: Ptr[scala.Byte]
Expand All @@ -37,7 +36,7 @@ private[lang] object StackTrace {
cursor: Ptr[scala.Byte],
ip: CUnsignedLong
): StackTraceElement =
cache.get().getOrElseUpdate(ip, makeStackTraceElement(cursor))
cache.getOrElseUpdate(ip, makeStackTraceElement(cursor))

@noinline private[lang] def currentStackTrace(): Array[StackTraceElement] = {
var buffer = mutable.ArrayBuffer.empty[StackTraceElement]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import scala.scalanative.unsafe._
import scala.scalanative.meta.LinktimeInfo.isMultithreadingEnabled
import scala.scalanative.runtime.libc.atomic_thread_fence
import scala.scalanative.runtime.libc.memory_order._
import java.util.concurrent.ConcurrentHashMap
import scala.collection.concurrent.TrieMap

trait NativeThread {
import NativeThread._
Expand Down Expand Up @@ -94,7 +94,7 @@ object NativeThread {
fromRawPtr[scala.Byte](castObjectToRawPtr(thread))

object Registry {
private val _aliveThreads = new ConcurrentHashMap[Long, NativeThread]()
private val _aliveThreads = TrieMap.empty[Long, NativeThread]
Copy link
Member

@armanbilge armanbilge Feb 8, 2023

Choose a reason for hiding this comment

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

Is there still a plan to port ConcurrentHashMap at a later point? I'd have to read the paper again but I think TrieMap makes different performance trade-offs than ConcurrentHashMap to support some special features.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, at some point I wish we port all the collections from JSR-166. The implementation ported from Scala.js is great, but I'm worried that it's not safe enough for usage in a multithreading environment.

Copy link
Member

Choose a reason for hiding this comment

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

The implementation ported from Scala.js is great, but I'm worried that it's not safe enough for usage in a multithreading environment.

Oh, definitely cannot use the current implementation in multi-threading :) we rely a lot on ConcurrentHashMap in Cats Effect.


private[NativeThread] def add(thread: NativeThread): Unit =
_aliveThreads.put(thread.thread.getId(), thread)
Expand All @@ -103,10 +103,7 @@ object NativeThread {
_aliveThreads.remove(thread.thread.getId())

def aliveThreads: scala.Array[NativeThread] =
_aliveThreads
.values()
.toArray()
.asInstanceOf[scala.Array[NativeThread]]
_aliveThreads.values.toArray

def onMainThreadTermination() = {
_aliveThreads.remove(MainThreadId)
Expand Down
20 changes: 15 additions & 5 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ object Build {
lazy val auxlib = MultiScalaProject("auxlib")
.enablePlugins(MyScalaNativePlugin)
.settings(mavenPublishSettings, commonJavalibSettings, disabledDocsSettings)
.dependsOn(nativelib)
.dependsOn(nativelib, clib)
.withNativeCompilerPlugin

lazy val scalalib: MultiScalaProject =
Expand Down Expand Up @@ -763,10 +763,20 @@ object Build {
Test / unmanagedSources ++= {
if (!shouldPartest.value) Nil
else {
val blacklist: Set[String] =
blacklistedFromFile(
(Test / resourceDirectory).value / scalaVersion.value / "BlacklistedTests.txt"
)
val blacklist: Set[String] = {
val versionTestsDir =
(Test / resourceDirectory).value / scalaVersion.value
val base =
blacklistedFromFile(versionTestsDir / "BlacklistedTests.txt")
val requiringMultithreading =
if (nativeConfig.value.multithreadingSupport) Set.empty[String]
else
blacklistedFromFile(
versionTestsDir / "BlacklistedTests-require-threads.txt",
ignoreMissing = true
)
base ++ requiringMultithreading
}

val jUnitTestsPath =
(scalaPartest / fetchScalaSource).value / "test" / "junit"
Expand Down
36 changes: 20 additions & 16 deletions project/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -335,10 +335,19 @@ object Settings {
}

// Get all blacklisted tests from a file
def blacklistedFromFile(file: File) =
IO.readLines(file)
.filter(l => l.nonEmpty && !l.startsWith("#"))
.toSet
def blacklistedFromFile(
file: File,
ignoreMissing: Boolean = false
): Set[String] =
if (file.exists())
IO.readLines(file)
.filter(l => l.nonEmpty && !l.startsWith("#"))
.toSet
else {
if (ignoreMissing) System.err.println(s"Ignore not existing file $file")
else throw new RuntimeException(s"Missing file: $file")
Set.empty
}

// Get all scala sources from a directory
def allScalaFromDir(dir: File): Seq[(String, java.io.File)] =
Expand Down Expand Up @@ -777,18 +786,13 @@ object Settings {
}
}

val useless =
path.contains("/scala/collection/parallel/") ||
path.contains("/scala/util/parsing/")
if (!useless) {
if (!patchGlob.matches(sourcePath))
addSource(path)(Some(sourcePath.toFile))
else {
val sourceName = path.stripSuffix(".patch")
addSource(sourceName)(
tryApplyPatch(sourceName)
)
}
if (!patchGlob.matches(sourcePath))
addSource(path)(Some(sourcePath.toFile))
else {
val sourceName = path.stripSuffix(".patch")
addSource(sourceName)(
tryApplyPatch(sourceName)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ scala/lang/primitives/BoxUnboxTest.scala
scala/lang/stringinterpol/StringContextTest.scala
scala/collection/SeqTest.scala
scala/collection/Sizes.scala
scala/collection/SetMapConsistencyTest.scala
scala/collection/mutable/OpenHashMapTest.scala
scala/collection/immutable/ListTest.scala
scala/collection/immutable/ListMapTest.scala
Expand Down Expand Up @@ -142,13 +143,6 @@ scala/runtime/FloatBoxingTest.scala
scala/collection/mutable/AnyRefMapTest.scala


#scala.collection.parallel._
scala/collection/NewBuilderTest.scala
scala/collection/parallel/immutable/ParRangeTest.scala
scala/collection/parallel/TaskTest.scala
scala/collection/ParallelConsistencyTest.scala
scala/runtime/ScalaRunTimeTest.scala

#j.l.reflect.Modifier
scala/reflect/macros/AttachmentsTest.scala
scala/collection/IteratorTest.scala
Expand All @@ -162,11 +156,6 @@ scala/tools/testing/AssertUtil.scala
scala/tools/testing/AssertUtilTest.scala
scala/tools/testing/AssertThrowsTest.scala

#s.c.c.TrieMap
scala/collection/concurrent/TrieMapTest.scala
scala/collection/SetMapConsistencyTest.scala
scala/collection/SetMapRulesTest.scala

#j.i.ObjectStream
scala/PartialFunctionSerializationTest.scala
scala/MatchErrorSerializationTest.scala
Expand All @@ -186,13 +175,17 @@ scala/collection/convert/NullSafetyToJavaTest.scala
# Concurrency primitives
scala/io/SourceTest.scala
scala/sys/process/ProcessTest.scala
scala/concurrent/impl/DefaultPromiseTest.scala

#============
## Tests fail

scala/collection/immutable/StreamTest.scala

### Deadlocks maybe needs j.u.c.ConcurrentLinkedQueue
scala/concurrent/impl/DefaultPromiseTest.scala
scala/collection/parallel/TaskTest.scala
scala/collection/NewBuilderTest.scala

#=====
## Assumes JUnit 4.12
scala/collection/immutable/RangeTest.scala
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
scala/collection/convert/MapWrapperTest.scala
scala/collection/concurrent/TrieMapTest.scala