From dbc6cafd116ee22e1c79826c4877c32eda390ebf Mon Sep 17 00:00:00 2001 From: Daniel Spiewak Date: Wed, 5 Feb 2025 09:16:58 -0600 Subject: [PATCH 1/3] Swapped out static tag approach with `ClassValue` cache (ht @xuwei-k) --- .../src/main/scala/cats/effect/IO.scala | 166 +++++++++--------- .../src/main/scala/cats/effect/IOFiber.scala | 8 +- 2 files changed, 91 insertions(+), 83 deletions(-) diff --git a/core/shared/src/main/scala/cats/effect/IO.scala b/core/shared/src/main/scala/cats/effect/IO.scala index 7840f2656a..a7b664625d 100644 --- a/core/shared/src/main/scala/cats/effect/IO.scala +++ b/core/shared/src/main/scala/cats/effect/IO.scala @@ -117,8 +117,6 @@ import Platform.static */ sealed abstract class IO[+A] private () extends IOPlatform[A] { - private[effect] def tag: Byte - /** * Like [[*>]], but keeps the result of the source. * @@ -1202,6 +1200,66 @@ private[effect] trait IOLowPriorityImplicits { object IO extends IOCompanionPlatform with IOLowPriorityImplicits with TupleParallelSyntax { + private[effect] val DispatchTable: ClassValue[Byte] = new ClassValue[Byte] { + // these technically get held indefinitely even though they don't *need* to be + // in theory, this shouldn't matter though, and it makes `computeValue` faster reducing warmup + private[this] val PureClass = classOf[IO.Pure[?]] + private[this] val ErrorClass = classOf[IO.Error] + private[this] val DelayClass = classOf[IO.Delay[?]] + private[this] val ReadTimeClass = classOf[IO.RealTime.type] + private[this] val MonotonicClass = classOf[IO.Monotonic.type] + private[this] val ReadEcClass = classOf[IO.ReadEC.type] + private[this] val MapClass = classOf[IO.Map[?, ?]] + private[this] val FlatMapClass = classOf[IO.FlatMap[?, ?]] + private[this] val AttemptClass = classOf[IO.Attempt[?]] + private[this] val HandleErrorWithClass = classOf[IO.HandleErrorWith[?]] + private[this] val CanceledClass = classOf[IO.Canceled.type] + private[this] val OnCancelClass = classOf[IO.OnCancel[?]] + private[this] val UncancelableClass = classOf[IO.Uncancelable[?]] + private[this] val UnmaskRunLoopClass = classOf[IO.Uncancelable.UnmaskRunLoop[?]] + private[this] val IOContClass = classOf[IO.IOCont[?, ?]] + private[this] val GetClass = classOf[IO.IOCont.Get[?]] + private[this] val CedeClass = classOf[IO.Cede.type] + private[this] val StartClass = classOf[IO.Start[?]] + private[this] val RacePairClass = classOf[IO.RacePair[?, ?]] + private[this] val SleepClass = classOf[IO.Sleep] + private[this] val EvalOnClass = classOf[IO.EvalOn[?]] + private[this] val BlockingClass = classOf[IO.Blocking[?]] + private[this] val LocalClass = classOf[IO.Local[?]] + private[this] val IOTraceClass = classOf[IO.IOTrace.type] + private[this] val ReadRTClass = classOf[IO.ReadRT.type] + private[this] val EndFiberClass = classOf[IO.EndFiber.type] + + override def computeValue(clazz: Class[_]): Byte = (clazz: @unchecked) match { + case PureClass => 0 + case ErrorClass => 1 + case DelayClass => 2 + case ReadTimeClass => 3 + case MonotonicClass => 4 + case ReadEcClass => 5 + case MapClass => 6 + case FlatMapClass => 7 + case AttemptClass => 8 + case HandleErrorWithClass => 9 + case CanceledClass => 10 + case OnCancelClass => 11 + case UncancelableClass => 12 + case UnmaskRunLoopClass => 13 + case IOContClass => 14 + case GetClass => 15 + case CedeClass => 16 + case StartClass => 17 + case RacePairClass => 18 + case SleepClass => 19 + case EvalOnClass => 20 + case BlockingClass => 21 + case LocalClass => 22 + case IOTraceClass => 23 + case ReadRTClass => 24 + case EndFiberClass => -1 + } + } + implicit final def catsSyntaxParallelSequence1[T[_], A]( toia: T[IO[A]]): ParallelSequenceOps1[T, IO, A] = new ParallelSequenceOps1(toia) @@ -2202,136 +2260,86 @@ object IO extends IOCompanionPlatform with IOLowPriorityImplicits with TuplePara // implementations private[effect] final case class Pure[+A](value: A) extends IO[A] { - def tag = 0 override def toString: String = s"IO($value)" } - private[effect] final case class Error(t: Throwable) extends IO[Nothing] { - def tag = 1 - } + private[effect] final case class Error(t: Throwable) extends IO[Nothing] private[effect] final case class Delay[+A](thunk: () => A, event: TracingEvent) - extends IO[A] { - def tag = 2 - } + extends IO[A] - private[effect] case object RealTime extends IO[FiniteDuration] { - def tag = 3 - } + private[effect] case object RealTime extends IO[FiniteDuration] - private[effect] case object Monotonic extends IO[FiniteDuration] { - def tag = 4 - } + private[effect] case object Monotonic extends IO[FiniteDuration] - private[effect] case object ReadEC extends IO[ExecutionContext] { - def tag = 5 - } + private[effect] case object ReadEC extends IO[ExecutionContext] private[effect] final case class Map[E, +A](ioe: IO[E], f: E => A, event: TracingEvent) - extends IO[A] { - def tag = 6 - } + extends IO[A] private[effect] final case class FlatMap[E, +A]( ioe: IO[E], f: E => IO[A], event: TracingEvent) - extends IO[A] { - def tag = 7 - } + extends IO[A] - private[effect] final case class Attempt[+A](ioa: IO[A]) extends IO[Either[Throwable, A]] { - def tag = 8 - } + private[effect] final case class Attempt[+A](ioa: IO[A]) extends IO[Either[Throwable, A]] private[effect] final case class HandleErrorWith[+A]( ioa: IO[A], f: Throwable => IO[A], event: TracingEvent) - extends IO[A] { - def tag = 9 - } + extends IO[A] - private[effect] case object Canceled extends IO[Unit] { - def tag = 10 - } + private[effect] case object Canceled extends IO[Unit] - private[effect] final case class OnCancel[+A](ioa: IO[A], fin: IO[Unit]) extends IO[A] { - def tag = 11 - } + private[effect] final case class OnCancel[+A](ioa: IO[A], fin: IO[Unit]) extends IO[A] private[effect] final case class Uncancelable[+A]( body: Poll[IO] => IO[A], event: TracingEvent) - extends IO[A] { - def tag = 12 - } + extends IO[A] + private[effect] object Uncancelable { // INTERNAL, it's only created by the runloop itself during the execution of `Uncancelable` - final case class UnmaskRunLoop[+A](ioa: IO[A], id: Int, self: IOFiber[?]) extends IO[A] { - def tag = 13 - } + final case class UnmaskRunLoop[+A](ioa: IO[A], id: Int, self: IOFiber[?]) extends IO[A] } // Low level construction that powers `async` private[effect] final case class IOCont[K, R](body: Cont[IO, K, R], event: TracingEvent) - extends IO[R] { - def tag = 14 - } + extends IO[R] + private[effect] object IOCont { // INTERNAL, it's only created by the runloop itself during the execution of `IOCont` - final case class Get[A](state: ContState) extends IO[A] { - def tag = 15 - } + final case class Get[A](state: ContState) extends IO[A] } - private[effect] case object Cede extends IO[Unit] { - def tag = 16 - } + private[effect] case object Cede extends IO[Unit] - private[effect] final case class Start[A](ioa: IO[A]) extends IO[FiberIO[A]] { - def tag = 17 - } + private[effect] final case class Start[A](ioa: IO[A]) extends IO[FiberIO[A]] private[effect] final case class RacePair[A, B](ioa: IO[A], iob: IO[B]) - extends IO[Either[(OutcomeIO[A], FiberIO[B]), (FiberIO[A], OutcomeIO[B])]] { - def tag = 18 - } + extends IO[Either[(OutcomeIO[A], FiberIO[B]), (FiberIO[A], OutcomeIO[B])]] - private[effect] final case class Sleep(delay: FiniteDuration) extends IO[Unit] { - def tag = 19 - } + private[effect] final case class Sleep(delay: FiniteDuration) extends IO[Unit] - private[effect] final case class EvalOn[+A](ioa: IO[A], ec: ExecutionContext) extends IO[A] { - def tag = 20 - } + private[effect] final case class EvalOn[+A](ioa: IO[A], ec: ExecutionContext) extends IO[A] private[effect] final case class Blocking[+A]( hint: Sync.Type, thunk: () => A, event: TracingEvent) - extends IO[A] { - def tag = 21 - } + extends IO[A] private[effect] final case class Local[+A](f: IOLocalState => (IOLocalState, A)) - extends IO[A] { - def tag = 22 - } + extends IO[A] - private[effect] case object IOTrace extends IO[Trace] { - def tag = 23 - } + private[effect] case object IOTrace extends IO[Trace] - private[effect] case object ReadRT extends IO[IORuntime] { - def tag = 24 - } + private[effect] case object ReadRT extends IO[IORuntime] // INTERNAL, only created by the runloop itself as the terminal state of several operations - private[effect] case object EndFiber extends IO[Nothing] { - def tag = -1 - } - + private[effect] case object EndFiber extends IO[Nothing] } private object SyncStep { diff --git a/core/shared/src/main/scala/cats/effect/IOFiber.scala b/core/shared/src/main/scala/cats/effect/IOFiber.scala index 5ad582aed5..38a6579ab6 100644 --- a/core/shared/src/main/scala/cats/effect/IOFiber.scala +++ b/core/shared/src/main/scala/cats/effect/IOFiber.scala @@ -254,7 +254,7 @@ private final class IOFiber[A]( * The cases have to use continuous constants to generate a `tableswitch`. * Do not name or reorder them. */ - (cur0.tag: @switch) match { + (IO.DispatchTable.get(cur0.getClass): @switch) match { case 0 => val cur = cur0.asInstanceOf[Pure[Any]] runLoop(succeeded(cur.value, 0), nextCancelation, nextAutoCede) @@ -332,7 +332,7 @@ private final class IOFiber[A]( if (error == null) succeeded(result, 0) else failed(error, 0) } - (ioe.tag: @switch) match { + (IO.DispatchTable.get(ioe.getClass): @switch) match { case 0 => val pure = ioe.asInstanceOf[Pure[Any]] runLoop(next(pure.value), nextCancelation - 1, nextAutoCede) @@ -403,7 +403,7 @@ private final class IOFiber[A]( onFatalFailure(t) } - (ioe.tag: @switch) match { + (IO.DispatchTable.get(ioe.getClass): @switch) match { case 0 => val pure = ioe.asInstanceOf[Pure[Any]] runLoop(next(pure.value), nextCancelation - 1, nextAutoCede) @@ -458,7 +458,7 @@ private final class IOFiber[A]( val ioa = cur.ioa - (ioa.tag: @switch) match { + (IO.DispatchTable.get(ioa.getClass): @switch) match { case 0 => val pure = ioa.asInstanceOf[Pure[Any]] runLoop(succeeded(Right(pure.value), 0), nextCancelation - 1, nextAutoCede) From 4fcbe938692b61fe00e0ce76a702a0a4b5def5ab Mon Sep 17 00:00:00 2001 From: Daniel Spiewak Date: Thu, 6 Feb 2025 16:49:23 -0600 Subject: [PATCH 2/3] Scalafmt --- core/shared/src/main/scala/cats/effect/IO.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/shared/src/main/scala/cats/effect/IO.scala b/core/shared/src/main/scala/cats/effect/IO.scala index a7b664625d..880f77ea46 100644 --- a/core/shared/src/main/scala/cats/effect/IO.scala +++ b/core/shared/src/main/scala/cats/effect/IO.scala @@ -1230,7 +1230,7 @@ object IO extends IOCompanionPlatform with IOLowPriorityImplicits with TuplePara private[this] val ReadRTClass = classOf[IO.ReadRT.type] private[this] val EndFiberClass = classOf[IO.EndFiber.type] - override def computeValue(clazz: Class[_]): Byte = (clazz: @unchecked) match { + override def computeValue(clazz: Class[?]): Byte = (clazz: @unchecked) match { case PureClass => 0 case ErrorClass => 1 case DelayClass => 2 @@ -2265,8 +2265,7 @@ object IO extends IOCompanionPlatform with IOLowPriorityImplicits with TuplePara private[effect] final case class Error(t: Throwable) extends IO[Nothing] - private[effect] final case class Delay[+A](thunk: () => A, event: TracingEvent) - extends IO[A] + private[effect] final case class Delay[+A](thunk: () => A, event: TracingEvent) extends IO[A] private[effect] case object RealTime extends IO[FiniteDuration] @@ -2331,8 +2330,7 @@ object IO extends IOCompanionPlatform with IOLowPriorityImplicits with TuplePara event: TracingEvent) extends IO[A] - private[effect] final case class Local[+A](f: IOLocalState => (IOLocalState, A)) - extends IO[A] + private[effect] final case class Local[+A](f: IOLocalState => (IOLocalState, A)) extends IO[A] private[effect] case object IOTrace extends IO[Trace] From b19fc72dcf3e01d3566f728d61e3532e7d03d6cb Mon Sep 17 00:00:00 2001 From: Daniel Spiewak Date: Thu, 6 Feb 2025 17:05:46 -0600 Subject: [PATCH 3/3] Fixed class computation --- core/shared/src/main/scala/cats/effect/IO.scala | 16 ++++++++-------- package-lock.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/shared/src/main/scala/cats/effect/IO.scala b/core/shared/src/main/scala/cats/effect/IO.scala index 880f77ea46..fffa72840c 100644 --- a/core/shared/src/main/scala/cats/effect/IO.scala +++ b/core/shared/src/main/scala/cats/effect/IO.scala @@ -1206,29 +1206,29 @@ object IO extends IOCompanionPlatform with IOLowPriorityImplicits with TuplePara private[this] val PureClass = classOf[IO.Pure[?]] private[this] val ErrorClass = classOf[IO.Error] private[this] val DelayClass = classOf[IO.Delay[?]] - private[this] val ReadTimeClass = classOf[IO.RealTime.type] - private[this] val MonotonicClass = classOf[IO.Monotonic.type] - private[this] val ReadEcClass = classOf[IO.ReadEC.type] + private[this] val ReadTimeClass = IO.RealTime.getClass + private[this] val MonotonicClass = IO.Monotonic.getClass + private[this] val ReadEcClass = IO.ReadEC.getClass private[this] val MapClass = classOf[IO.Map[?, ?]] private[this] val FlatMapClass = classOf[IO.FlatMap[?, ?]] private[this] val AttemptClass = classOf[IO.Attempt[?]] private[this] val HandleErrorWithClass = classOf[IO.HandleErrorWith[?]] - private[this] val CanceledClass = classOf[IO.Canceled.type] + private[this] val CanceledClass = IO.Canceled.getClass private[this] val OnCancelClass = classOf[IO.OnCancel[?]] private[this] val UncancelableClass = classOf[IO.Uncancelable[?]] private[this] val UnmaskRunLoopClass = classOf[IO.Uncancelable.UnmaskRunLoop[?]] private[this] val IOContClass = classOf[IO.IOCont[?, ?]] private[this] val GetClass = classOf[IO.IOCont.Get[?]] - private[this] val CedeClass = classOf[IO.Cede.type] + private[this] val CedeClass = IO.Cede.getClass private[this] val StartClass = classOf[IO.Start[?]] private[this] val RacePairClass = classOf[IO.RacePair[?, ?]] private[this] val SleepClass = classOf[IO.Sleep] private[this] val EvalOnClass = classOf[IO.EvalOn[?]] private[this] val BlockingClass = classOf[IO.Blocking[?]] private[this] val LocalClass = classOf[IO.Local[?]] - private[this] val IOTraceClass = classOf[IO.IOTrace.type] - private[this] val ReadRTClass = classOf[IO.ReadRT.type] - private[this] val EndFiberClass = classOf[IO.EndFiber.type] + private[this] val IOTraceClass = IO.IOTrace.getClass + private[this] val ReadRTClass = IO.ReadRT.getClass + private[this] val EndFiberClass = IO.EndFiber.getClass override def computeValue(clazz: Class[?]): Byte = (clazz: @unchecked) match { case PureClass => 0 diff --git a/package-lock.json b/package-lock.json index 7c5355ceb9..d9c8e30247 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "cats-effect", + "name": "series-3.6.x", "lockfileVersion": 2, "requires": true, "packages": {