typelevel / typelevel/cats-effect

Use Scala Native RTTI class IDs for switch statement in `IO` runloop

Open
#3,712 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

:microscope: experiment
Dominant language
Scala
Stars
2.2k
Forks
576
Avg merge
2d 11h
Merged PRs (30d)
18

Description

In Scala Native, at linking time every class is assigned an ID, as part of its RTTI (runtime type info?). Furthermore, the id of every subclass of a class is sequential i.e. every subclass of IO occupies some ID in a sequential range of IDs.

This suggests we can try an optimization similar to https://github.com/typelevel/cats-effect/pull/3471, where we avoid the virtual dispatch on the IO#tag method in the IO runloop. The difference is that we don't need to increase the memory footprint of every IO instance, although we do have to chase a pointer to find the tag.

A small demo program below prints out the IDs for several IO subclasses. Unfortunately it reveals a tricky issue that they seem to be assigned out-of-order. I'm not sure if there's anything we can do about that, because it might completely tank this idea. 😕

//> using dep org.typelevel::cats-effect::3.5.1
//> using platform native
//> using nativeVersion "0.4.14"

import cats.effect.IO
import scala.concurrent.duration.*
import scalanative.runtime.Intrinsics.*
import cats.effect.kernel.Cont
import cats.arrow.FunctionK
import cats.effect.kernel.MonadCancel

def getId(io: IO[_]): Int =
  loadInt(elemRawPtr(loadRawPtr(castObjectToRawPtr(io)), 8))

@main def main =
  println(getId(IO.pure(())))
  println(getId(IO.raiseError(new Exception)))
  println(getId(IO.delay(())))
  println(getId(IO.realTime))
  println(getId(IO.monotonic))
  println(getId(IO.executionContext))
  println(getId(IO.unit.map(_ => ())))
  println(getId(IO.unit.flatMap(_ => IO.unit)))
  println(getId(IO.unit.attempt))
  println(getId(IO.unit.handleErrorWith(_ => IO.unit)))
  println(getId(IO.canceled))
  println(getId(IO.unit.onCancel(IO.unit)))
  println(getId(IO.unit.uncancelable))
  println("id for `Uncancelable.UnmaskRunLoop`")
  println(getId(IO.cont(new Cont[IO, Unit, Unit] {
    def apply[G[_]](implicit G: MonadCancel[G, Throwable]) = (_, _, _) => G.unit
  })))
  println("id for `IOCont.Get`")
  println(getId(IO.cede))
  println(getId(IO.unit.start))
  println(getId(IO.racePair(IO.unit, IO.unit)))
  println(getId(IO.sleep(1.second)))
  println(getId(IO.unit.evalOn(null)))
  println(getId(IO.blocking(())))
381
384
383
395
396
391
380
390
389
398
394
392
397
id for `Uncancelable.UnmaskRunLoop`
388
id for `IOCont.Get`
382
386
393
385
387
390

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the Scala Native demo in the issue and compare the printed RTTI class IDs for the IO subclasses. Read the IO runloop and the linked cats-effect pull request to assess whether RTTI IDs can replace virtual dispatch without relying on sequential ordering. Done would require a viable switch-based optimization and evidence that the runloop remains correct and improves performance.

Written by the indexing model from the issue text.

Assessment

Tech stack
scala
Domain
backend, performance
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.