Does the CPS transformation happen in IR? I'm tryi...
# compiler
y
Does the CPS transformation happen in IR? I'm trying to make continuations shallow-cloneable, and hence I'd like to access the fields that the state machine stores so that I can copy them over to a new version of it. Would an IR plugin be able to do that, or do I have to resort to bytecode manipulation? My plan is to make a new code color of
@Multishot suspend fun
and make it so that the continuations that the special
suspendCoroutineMultishot
provides always resumes a clone of the normal CPS continuation. I think that would be sufficient to implement multishot behaviour?
r
Last time I tried the CPS transformation happened when lowering to the platform bytecode and was not something we could just change in the IR phase, not sure if that has changed since then.
y
That would make sense because inlining also happened at bytecode level, but my understanding is that inlining either has been moved or is in the process of being moved to IR, so I'm hoping that the CPS transformation is also being moved
Cloning on JS would be easy enough, I guess. Jvm seems harder but maybe bytecode manipulation wouldn't be too horrible. I have no idea how I would clone on native though
e
how do you envision being able to clone arbitrary values? I don't think that can be done safely
y
Shallow cloning continuations. A continuation (at least the raw one obtained from
suspendCoroutineUninterceptedOrReturn
) stores your local variables in its fields (if you recall, suspend functions are compiled into state machines, represented by those continuations). If you can clone the state of the state machine (i.e its local variables and what steps its at), you can run the state machine multiple times. Again, all this would involve is just making a shallow copy of the fields of the state machine, nothing more. In other words: I want to clone continuations, and shallowly at that. Not taking about general cloning of any value (that's obviously way more involved).
e
that seems like it could break existing code, such as a suspend point inside a
.use {}
resuming multiple times and closing the same resource
y
Yes of course. More complicated resource management would be needed. However, this would never happen to existing code though. The intention here is that it would be a new code colour, hence the code using it would be keeping that in mind and would explicitly be marked. It would be a logical error that one is closing a resource on every call like that, but not a compiler error or anything. For such special resource management, one needs a way to be able to cancel a resource upon suspension and then reacquire it when resumed. Such a mechanism is already known by names like dynamic wind
e
if it's a new code color (that you can't call normal
suspend
functions in nor call from normal
suspend
functions) then what's the effective difference to
@Composable
?
y
Sorry, I should clarify. You'd be able to call normal
suspend
functions inside, but
suspend
functions can't call you. Just like how suspend functions can call pure functions but not the other way around. The difference to
@Composable
is that
@Composable
doesn't actually do a CPS transform or anything like that. In fact, I have implemented this in terms of
@Composable
, and I've been able to get it to work with a few tricks, but one has to essentially rerun the function every time because there's no state machine. The performance suffers also vs a native solution
e
it's not a CPS transform but #squarelibraries molecule does
Copy code
launchMolecule {
  val value1 by flow1.collectAsState()
  val value2 by flow2.collectAsState()
  ...
  value1 + value2
}
by
@Composable
one has to essentially rerun the function every time
why's that? it's chopped up into restartable segments
y
That's where I started my journey on this. The issue with that is it isn't a
flatMap
type of comprehension. Yes this type can be useful (and in fact would be supported by multishot continuations) but multishot continuations support more than what molecule can do. > it's chopped up into restartable segments That happens for composable function calls, but not for the code in between. If a composable gets invalidated, execution doesn't jump to the point where it was invalidated, it jumps to the block where it was invalidated. In your example, if you have a
println
before a
collectAsState
, that
println
runs every single time the state updates, even though it occurs before you even collected the flow. This can be worked around in Compose if you basically manually turn your function into a state machine. The way I do it is I have a special token `remember`ed for every shift point, and I wrap any side effect or pure-but-expensive-calculation into an
effect{ }
block which memoises its results and only recalculates if the shift point we're resuming at has been reached. This is messy and error-prone though, and the solution to do this with the pre-exisiting CPS transform is a lot simpler, it just needs cloning support. This can be worked around with
Keep in mind that the coroutines KEEP states (emphasis mine): > Note: That is the key difference between coroutines in Kotlin and first-class delimited continuations in functional languages like Scheme or continuation monad in Haskell. The choice to support only resume-once continuations is purely pragmatic as none of the intended use cases need multi-shot continuations. However, multi-shot continuations can be implemented as a separate library by suspending coroutine with low-level coroutine intrinsics and cloning the state of the coroutine that is captured in continuation, so that its clone can be resumed again. My argument is that such cloning is hard to do in a multiplatform context (maybe even impossible), so if a compiler plugin can be written to make that cloning easier (or even better, if the compiler can expose an intrinsic for such cloning), then multishot coroutines can be supported in a separate library.
r
@Youssef Shoaib [MOD] I really hope you get to the bottom of this. I’m cheering for you to have multi shot continuations one day in kotlin 🙏