Monday, 25 August 2025

Python Adaptive Specializing Interpreter

It's clear that I have a fascination with the interaction between interpreters and JIT's, optmizations, deoptimizations and so on. I've written multiple posts about that, with this one being the most recent. I recently came across this interesting thesis where in the introduction it mentions:

For both variants, we can quicken certain operations. Quickening entails replacing one operation with another, which usually handles a subset of possible values or a more restrictive set of preconditions but performs the operation quicker. In tree-walk interpreters, this is performed by node replacement, and in bytecode-based interpreters, by changing the instruction stream, replacing one instruction with another. In both cases, the target of the dispatch changes. How this is implemented in Operation DSL is detailed in section 3.7.

The "replacing one instruction with another" suddenly reminded me of something I had read some months ago regarding Python performance improvements but that I had forgotten to dive into and had indeed forgotten. I'm talking about Adaptive Specializing Interpreter, that is something pretty surprising to me. I've talked in my previous posts about interpreters that find hotspots in your code and send those hot methods to a fast JIT compiler to turn them into native code. Then that native code continues to be monitored and if it's hot enough it's sent to a more aggressive JIT compiler that spends more time in producing a more optimized native code. Then we have cases where the code has to be deoptimized, returning the function to its interpreted form, and the cycle starts again. But the idea of monitoring the code to find specific bytecode instructions (opcodes) that can be replaced by an optimized (specialized/quickened) version of that bytecode instruction is something that was pretty new to me

As of today (Python 3.13) the main Python environment, CPython, does not come with a JIT compiler (an experimental one is due for 3.14 I think). CPython just uses a bytecode interpreter (historical note: The move from the initial tree-walk interpreter to a bytecode interpreter happened between Python 0.9.x and Python 1.0, likely around 1992–1993 during the pre-1.0 development phase). Python 3.11 implemented PEP-659 - Specializing Adaptive Interpreter as part of the Faster CPython project. It introduced some bytecode instructions that are generic (for example the one for adding 2 items, BINARY_OP_ADD (+)) and that if a constant execution pattern is found will be replaced (specialized/quickened) by an specialized version ('BINARY_OP_ADD_FLOAT', 'BINARY_OP_ADD_INT', 'BINARY_OP_ADD_UNICODE'). If that pattern changes the instruction will be replaced by the initial, generic bytecode. This discussion has some interesting information.

You probably know that (as I mention in this post) Python compiles functions to code objects, and then each function object points to a code object (via __code__ attribute). A code object has a co_code attribute pointing to a bytes object containing the bytecodes.

bytes objects are inmutable, so the bytecode specialization has to happen in another structure. I could not find much information about this, so ChatGPT came to the rescue. So yes, there's an additional structure that contains a mutable copy of the bytecodes. It's from that structure that the interpreter reads the bytecodes to execute for that given function, and applies adaptations/specializations/quickening as it sees fit.

  • The co_code itself remains immutable and is not rewritten at runtime. It continues to contain the canonical, "baseline" bytecode sequence as emitted by the compiler.
  • When a code object is executed, CPython creates an internal _PyCodeRuntime structure (not exposed to Python), which contains a mutable copy of the bytecode in a field called co_firstinstr (technically in co_warm → co_warm.instructions).
  • That runtime bytecode buffer is where the interpreter patches in "quickened" instructions (specialized opcodes). For example, a generic BINARY_OP might be replaced at runtime with BINARY_OP_ADD_INT if it sees enough hot integer additions.

That mutable copy of the bytecodes seem to be referenced from the code object via a private _co_code_adaptive attribute (but this is an internal, undocumented detail that can change from version to version). Python allows us to very easily check the bytecodes for a given function by using the standard dis module: dis.dis(my_function). By default dis.dis shows the immutable bytecodes in co_code, but since python3.12 we can use the adaptive=True flag, to see the adapted/quickened instruction. This is pretty amazing, cause we can so easily see how a function bytecodes evolve over time!


import sys, dis

def f(a, b):
    return a + b

print("before warming up")
dis.dis(f, adaptive=True)  # Only in 3.12+
# BINARY_OP                0 (+)

# Warm it up
for _ in range(10_000):
    f(1, 2)

# Disassemble with quickening shown
print("after warming up with ints")
dis.dis(f, adaptive=True)
#BINARY_OP_ADD_INT        0 (+)

# now let's try to break the quickening by passing strings rather than ints
print("first call with strings")
f("a", "b")
dis.dis(f, adaptive=True)
# it's still quickened
#BINARY_OP_ADD_INT        0 (+)

print("second call with strings")
f("c", "b")
dis.dis(f, adaptive=True)
# it's still quickened
#BINARY_OP_ADD_INT        0 (+)

print("Warm it up again, this time with strings")
for _ in range(10_000):
    f("a", "b")

print("after warming up again")
dis.dis(f, adaptive=True)
# BINARY_OP_ADD_UNICODE    0 (+)

So initially the bytecode for an addition of 2 values uses the BINARY_OP opcode (python is a dynamic language where a and b could be of any type, so BINARY_OP is a generic (and slower) instruction for summing up any value). Then we do a good bunch of additions all of them with int values, so that the interpreter decides to specialize the generic addition to a fast BINARY_OP_ADD_INT opcode. After that we do a couple of invokations using strings rather than ints. The specialized opcode checks if the operands are the expected types (here, two ints), as they are not it falls back to the generic implementation of the operation (the slow path), but for the moment it still keeps the specialized opcode. It takes note of these divergences so that if they continue it will revert the specialization. The thing is that in my tests I have not managed to find the number of failed executions that will make the interpreter to revert the specialization, what we can see is that after a good bunch of executions using strings, the int specialization is changed to a string specialization (BINARY_OP_ADD_UNICODE).

In some previous post I mentioned some crazy Python projects that manipulate functions by creating a new code object (an instance of types.CodeType) based on the original one, with a modified version (adding extra instructions, whatever) of its bytecodes, and assigns it to the function. How does this play with the adaptive version of the code? Well, thanks to ChatGPT we learn that the adaptation process starts again:

  • The quickened bytecode (co_code_adaptive) is built lazily, the first time the interpreter executes a CodeType.
  • It is not stored permanently in the CodeType; rather, it is in a per-runtime structure that references the original co_code.
  • If you assign a different code object to a function (func.__code__ = new_code), that’s a new CodeType with its own co_code_adaptive buffer, initially empty.
  • Therefore, execution will start again with baseline opcodes and caches, and the specializing interpreter will re-warm and re-specialize.

Thursday, 7 August 2025

Python Decorators Implementation

There's something in the inner workings of the amazing multimethod library that we saw in my previous post that has had me pretty confused. In that post I outline how the multidispatch decorator works internally, but for the multimethod decorator, things are more complex. From my previous post we know we use it like this:


class Formatter:
    def __init__(self, wrapper: str):
        self.wrapper = wrapper

    @multimethod
    def format(self, item: str, starter: str):
        return f"{starter}{self.wrapper}{item}{self.wrapper}"
    
    @multimethod
    def format(self, item: int, starter: str):
        return f"{starter}{self.wrapper * 2}{item}{self.wrapper * 2}"   

multimethod is a class based decorator. In the first invokation it returns an instance of the multimethod class, that will store the format function and its signature. For each new invokation of the decorator it has to store each of those additional overload functions and signatures in that already existing instance, rather than creating a new instance each time. For doing that, the decorator checks if in the current scope (the class declaration scope) already exists a variable with the name of the function being decorated that already points to an instance of multimethod. For that it inspects the previous frame in the stack, like this (taken from its source code):


    def __new__(cls, func):
        homonym = inspect.currentframe().f_back.f_locals.get(func.__name__)
        if isinstance(homonym, multimethod):
            return homonym
        ...

That's really, really nice code, but it's another thing what I could not grasp. We know that Python decorators are just callables (functions or classes) that are invoked receiving the function (or class) being decorated as parameter. So they are normally explained like this:


@my_deco
def fn(): 
   # whatever

Is (in principle) just equivalent (syntactic sugar) to this:


def fn():
   # whatever
fn = my_deco(fn)

The problem is that it would mean that our example above is indeed translated by the Python compiler into something like this:



    def format(self, item: str, starter: str):
        return f"{starter}{self.wrapper}{item}{self.wrapper}"
    format = multimethod(format)
    # here format is pointing to a multimethod instance, good
    
   
    def format(self, item: int, starter: str):
        return f"{starter}{self.wrapper * 2}{item}{self.wrapper * 2}" 
    # but here format is pointing the the function that we've just defined, so when we apply the decorator again and it does the isinstance(homonym, multimethod) check, it will be False! so this can not work
    format = multimethod(format)

I've explained the problem in the comments. When defining the second format function, the format variable in the current scope is set to that second function, so it's no longer pointing the the multimethod instance previously created, so the isinstance(homonym, multimethod) check will be False! and a new multimethod instance will be created, so the whole thing can not work!

Well, after discussing this with Claude AI we've found an explanation. When applying a decorator to a function, the function definition doesn't immediately overwrite the namespace (setting a variable with the name of the function to point to the function), what is really happening is this:

  1. def format(...) creates a temporary function object
  2. @multimethod is applied to that temporary function object
  3. The decorator returns an object (which could be the existing multimethod)
  4. Only then does format = assignment happen

So indeed a decorator works more like this:


format = my_deco(
	def format():
	    # whatever
)

But as Python lacks statement lambdas, the above code is not valid, and hence the different articles explaining decorators can not use that as the pseudo-code for what the runtime really does, and use an inaccurate approximation. Usually that's not a problem, but for this very specific case that approximation prevented me from envisioning how this particular decorator works.

Friday, 1 August 2025

Visitor vs Multiple Dispatch

The Visitor pattern has always felt a bit odd to me. The thing is that more than as a good practice, it was born to make up for a limitation, the lack of multiple dispatch (aka multimethods) in most programming languages. Being forced to have a visit() method in the classes that are going to be visited feels terribly intrusive to me, it violates the separation of concerns principle. So, when we have a language that supports multiple dispatch (either directly in the language of through some library) we should favor that and forget about Visitors.

That said, Python has no support for multiple dispatch at the language level, but has at least 2 interesting libraries that leverage Python's dynamism and introspection to provide us with the feature. I have only tried multimethod, and it's pretty amazing. I'll show some examples.


from multimethod import multimethod

# multimethod does not support keyword arguments

class Formatter:
    def __init__(self, wrapper: str):
        self.wrapper = wrapper

    @multimethod
    def format(self, item: str, starter: str):
        return f"{starter}{self.wrapper}{item}{self.wrapper}"
    
    @multimethod
    def format(self, item: int, starter: str):
        return f"{starter}{self.wrapper * 2}{item}{self.wrapper * 2}"
    
f1 = Formatter("|")
print(f1.format("aaa", "- "))

print(f1.format(25, "- "))

# I can even register new overloads to an existing one
@Formatter.format.register
def _(self, item: bool):
    return f"{self.wrapper * 3}{item}{self.wrapper * 3}"

print(f1.format(True))

# and I can even overwrite an existing overload!
@Formatter.format.register
def _(self, item: int, starter: str):
    return f"{starter}{self.wrapper * 5}{item}{self.wrapper * 5}"

print(f1.format(25, "- "))

# - |aaa|
# - ||25||
# |||True|||
# - |||||25|||||

# but there's one limitation, keyword arguments do not work
#print(f1.format(item="bbb", starter="- "))
#print(f1.format(starter="- ", item="bbb"))
# multimethod.DispatchError

Well, the code is self-explanatory. I can define different overloads of a same method using the multimethod decorator, and the method invocation will be redirected to the right method based on the parameters. There's one limitation though, this redirection only works when using positional parameters. If we use named parameters, it fails with a multimethod.DispatchError. To work with named parameters we have to use the multidispatch decorator.


from multimethod import multidispatch 

# multidispatch supports keyword arguments  
class Formatter:
    def __init__(self, wrapper: str):
        self.wrapper = wrapper

    @multidispatch
    def format(self, item: str, starter: str):
        return f"{starter}{self.wrapper}{item}{self.wrapper}"
    
    # @multidispatch
    # def format(self, item: int, starter: str):
    #     return f"{starter}{self.wrapper * 2}{item}{self.wrapper * 2}"

    @format.register
    def _(self, item: int, starter: str):
        return f"{starter}{self.wrapper * 2}{item}{self.wrapper * 2}"

    
f1 = Formatter("|")
print(f1.format("aaa", "- "))

print(f1.format(25, "- "))

print(f1.format(starter="- ", item="bbb"))
print(f1.format(item=25, starter="- "))

# I can register new overloads to an existing class!
@Formatter.format.register
def _(self, item: bool):
    return f"{self.wrapper * 3}{item}{self.wrapper * 3}"

print(f1.format(True))
 
# and I can even overwrite an existing overload!
@Formatter.format.register
def _(self, item: int, starter: str):
    return f"{starter}{self.wrapper * 5}{item}{self.wrapper * 5}"

print(f1.format(item=25, starter="- "))

#  - |aaa|
# - ||25||
# - |bbb|
# - ||25||
# |||True|||
# - |||||25|||||


This multidispatch decorator is implemented quite differently from the multimethod decorator, so apart from supporting named parameters, you use it differently, via the register method. Let's see how the whole thing works.

First of all, let's revisit how class creation works in Python. When the runtime comes across a class declaration it executes the statements inside that declaration (normally we mainly have function definitions, but we can have also assignments, conditionals...) and the different elements (methods and attributes) defined there are added to a namespace object (sort of dictionary). Then, the class object is created by invoking type(classname, superclasses, namespace) (or if the class is using a metaclass other than type: MyMeta(classname, superclasses, namespace, **kwargs)). You can further read about this in these 2 previous posts [1] and [2]

multidispatch (same as multimethod) is a class based decorator, so when you do: "@multidispatch def format()..." you are creating an instance of the multidispatch class and adding a "format" entry in the class namespace pointing to it. Then, you decorate the other "overloads" of the function with calls to the register() method of that multidispatch instance, so the instance will store the different overloads with their corresponding signatures. Notice that because of its different internal implementation, you can not name the overload functions with the same name of the first one (that will become the "public" name for this multiple dispatch method), we use a commodity name like "_". Finally the class object will be created by inovoking type with a namespace object that contains this instance of the multidispatch class. Obviously the multidispatch class is callable, so it has a __call__ method that when invoked searches through its list of registered overloads based on the signature.

Notice in both samples above that after having declared a class we still can add additional overloads to the class using the register method (we can even overwrite an existing overload). This is a particularly nice feature as your class remains open

Friday, 25 July 2025

Python Conditional Collection Literals

I've previously mentioned that I regularly visit the discuss python forum (the ideas and PEP's sections). You frequently find there interesting ideas, and though normally they will be rejected and will never be incorporated into python, learning about them is really worth. You'll find ideas that are present in other languages and you were not aware of, and alternative ways to achieve what the idea proposes by using available Python techniques. One of those ideas that recently caught my attention is Conditional collection literals. It proposes a syntax for defining collections literals in which some elements are added or omitted based on one condition. Let's see an example to make it clear:


my_list = [
    elem_1,
    elem_2 if condition,
    elem_3,
]


OK, this is something rarely needed, but the idea looks pretty interesting. Reading the ensuing discussion I've come up with an alternative that looks pretty good to me, just using a sentinel value (OMIT) to indicate that that element must be omitted and function performing the filtering. Let's see it (it's two functions, one for list-like collections and one for dictionaries:


from typing import Any, Callable, Iterable, TypeVar

T = TypeVar('T')
K = TypeVar('K')
V = TypeVar('V')

# OMIT = object() # better use a class to have it available for typing
class OMIT: pass

def conditional_list(*items: T | type[OMIT]) -> list[T]:
    return [it for it in items if it is not OMIT]

def conditional_dict(*pairs: tuple[K, V] | type[OMIT]) -> dict[K, V]:
    return dict(pair for pair in pairs if pair is not OMIT)


def test_conditional_list():
    l1 = conditional_list(
        "a",
        "b",
        "c" if False else OMIT,
        "d",
        "e" if True else OMIT,
    )
    print(l1)

def test_conditional_dict():
    d1 = conditional_dict(
        ("a", "AA"),
        ("b", "BB"),
        ("c", "CC") if False else OMIT,
        ("d", "DD"),
        ("e", "EE") if True else OMIT,
    )        
    print(d1)

test_conditional_list()
print("------------------------")
test_conditional_dict()

# ['a', 'b', 'd', 'e']
# ------------------------
# {'a': 'AA', 'b': 'BB', 'd': 'DD', 'e': 'EE'}


For the OMIT "sentinel" value I was using initially an object instance, but in order to have something that we can use in the type annotations I've ended up using a class. There's a deferred PEP-661 (but it seems it could be close to being approved by the Steering Council) for adding a utility to define sentinel values. There's an interesting discussion about it here and here

Friday, 18 July 2025

Kotlin Coroutines Revisited. Part 3: Event Loops and More

Part III of this series of posts [1] and [2] revisiting some Kotlin coroutines concepts (that' I've managed to better understand thanks to some GPT (Claude) discussions). This time we'll see a pretty interesting low level detail about the Kotlin asynchronous system. JavaScript and Python asynchrony revolves around an event loop. This event loop is not accessible from JavaScript, but in Python we even can interact with it (though this is rarely necessary unless we are developing some low level library).

Main idea: We can say that in asynchronous systems there are 2 main actions. Waiting for IO events to happen, and managing a queue with the tasks to be invoked (the callbacks that were set in our awaitables (Promises, Futures), resuming continuations...) when those IO events (new data available) happen. JavaScript and Python asyncio manage both aspects, while kotlinx.coroutines manages the task to be invoked, but the awaiting of IO events is done by additional libraries.

There are many articles about the node.js event loop, describing it with different levels of detail (which makes it confusing some times), but the very basic idea is that we have a loop that waits for IO events (a network response, a file read, a timer...) using epoll (or kqueue or whatever the OS provides). When those events happen, the callback for the event goes to the macrotask queue, and when that callback is executed it will normally resolve or reject a Promise, and the "then-catch callbacks" for that Promise go to the microtask queue. The mechanism in Python is similar. When the last function in a chain of async calls performs a real asynchronous operation, it will obtain a file descriptor (fd) and add it to the event loop list of "events to watch" with ev_loop.add_reader(fd, callback_fn). It also creates a Future object (fut1). That callback_fn will take care of invoking fut1.set_result() sometime in the future, which in turn will invoke Future._schedule_callbacks. This _schedule_callbacks will add to the event loop queue of actions the callbacks that had been set in that Future via add_done_callback(). This is done via loop.call_soon(callback, self, context=ctx) (that instructs the event loop to run those callbacks in its next iteration).

The thing is that in both JavaScript and Python a single event loop (running in a single thread) manages, in the different phases of each of its iterations, both awaiting for the IO events that will resolve/complete Promises/Futures (awaitables) and executing the callbacks for the resolution/completion of those Promises/Futures (callbacks that will take care of resuming our paused/suspended async functions/coroutines).

In Kotlin this is a bit different. Let's say that the awaiting for the IO events and the "execution of the callbacks" (that in Kotlin means resuming the suspend functions (the coroutine) via the continuation mechanism), happen in 2 different levels. We should already know about Coroutine Dispatchers, that are an essential part of the Kotlin coroutines machinery. These Dispatchers decide where to start the coroutine and where to resume it. "Coroutine Dispatchers determine the thread or thread pool where a coroutine executes. They play a critical role in managing how and where coroutines run". It can be in a thread pool or in a thread running an event loop. Dispatcher.IO and Dispatcher.Default use a thread pool. Dispatcher.Main will use the event loop in the Main UI Thread (in Android, Java FX, Swing... applications), and a coroutine created with the runBlocking coroutine builder will also run in an event loop (an EventLoop is indeed a Coroutine Dispatcher, as you can see here):
internal abstract class EventLoop : CoroutineDispatcher()

So those dispatchers (part of the kotlinx.coroutines library) manage the start and resumption of our coroutines, but what about waiting for the IO events (IO asynchronous calls)? That depends on the different libraries that we use for those IO operations, it's not part of the kotlinx.coroutines library, but a lower level layer. If we use netty for performing http requests I think it will manage the low level IO via some kind of low level event loop based on NIO (something like this). Other libraries performing non blocking IO will use its own event loop (or a thread pool). When the data is available, their callbacks will invoke continuation.resume, and we'll move back into our Kotlin dispatchers (the upper level layer). This means that in a same process we can have multiple event loops running, a high level one for the Kotlin dispatchers layer and 1 or more low level ones (using low level mechanisms like NIO, epoll) for the different non blocking IO libraries. The same happens for thread pools. This can feel like a bit of redundant.

On one hand the JavaScript and Python async systems seem more integrated. Python IO libraries have access not just to Futures (on which to set results), but to the asyncio event loop itself where they can register file descriptors for the IO operations, as the asyncio event loop watches for IO events on those descriptors (via epoll or whatever). In Kotlin that's not like that because kotlinx.coroutines does not provide a mechanism for watching for the IO events (it stays in an upper level, it does not go down to the epoll and file descriptors underworld), it's the libraries who have to take care of that at that lower level stuff, and then jump into the upper level by invoking continuation.resume through a callback.

On the other hand, the Kotlin asynchronous model is more versatile, with its powerful dispatchers, that can use a single thread event loop and/or thread pools. Indeed, Dispatcher.IO uses a Thread pool optimized for IO operations and Dispatcher.Default a thread pool optimized for CPU bound operations.

Wednesday, 9 July 2025

Kotlin Coroutines Revisited. Part 2: Suspension

Part II of this series of posts that I started last week revisiting some Kotlin coroutines concepts (that' I've managed to better understand thanks to some GPT (Claude) discussions). We'll deal today with an essential part, the suspension mechanism. We know (and if you don't know it, probably this post is not for you) that each suspend function is transformed by the Kotlin compiler into a state machine. When we have a chain of suspend function calls (suspend functionA calls suspend functionB that calls suspend functionC...) the way these functions are linked (now that because of suspension and resumption we don't have then "linked" in a normal call stack) so that when functionC completes functionB gets resumed in the point where it got suspended/paused is by means of Continuation objects (callbacks on steroids). In JavaScript or Python async functions return a Promise or a Future, and it's those objects what indicates that a function is being "suspended". But in Kotlin where we pass continuations rather than returning "awaitables", how does a suspend function (the "leaf" function in our chain of suspend calls) ultimately communicate its caller that it's suspending? (it could suspend or it could just return a normal value sequentially depending on a conditional).

For a suspension to happen the last suspend function in the call (the one performing a low level asynchronous IO operation) has to invoke suspendCoroutine or suspendCancellableCoroutine, that in turn have will return the special marker value COROUTINE_SUSPENDED if the suspension really must take place. This COROUTINE_SUSPENDED value will be managed by the state machines created by the compiler for the different suspend functions and when a suspension must take place it will propagate through the stack (this suspension indicator is a normal return, so it propagates through the normal stack, then, when the suspend function "returns" a value asynchronously, it will propagate through the continuations chain).

suspendCoroutine and suspendCancellableCoroutine are intrinsic compiler functions. They're the only way to actually create a suspension point in the execution.

This is a very, very rough approximation (the signature is wrong and this state machine goes into a class with additional logic, it's not just a function) to how the state machine corresponding to a suspend function looks like:


// Conceptually, your userFunction becomes something like:
fun userFunction$stateMachine(continuation: Continuation, label: Int): Any? {
    when (label) {
        0 -> {
            // Initial call
            val result = networkCall(continuation.with(label = 1))
            if (result == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
            // Fall through to label 1
        }
        1 -> {
            // Resumed here - no "return" up the stack
            val networkResult = continuation.getResult()
            return processResult(networkResult)
        }
    }
}

And, how do these so special suspendCoroutine and suspendCancellableCoroutine functions work? Both functions receive a block (that expects as Continuation as parameter), and it's this block who will perform (or not) a low level asynchronous operation that once ready will call us back, meaning that in between our suspend functions will get suspended (to be resumed when we are called back). Both suspendCoroutine and suspendCancellableCoroutine end up calling suspendCoroutineUninterceptedOrReturn but suspendCoroutine passes over to the block a Continuation and suspendCancellableCoroutine passes over a CancellableContinuation. So in the end the magic happens in suspendCoroutineUninterceptedOrReturn. That's an intrinsic function, there's no Kotlin code for it. "Intrinsic compiler functions" are special functions that don't have normal implementations - instead, the compiler has built-in knowledge of what they do and generates specific bytecode for them.

I'll show now the code that the GPT (Claude) came up with during our discussions.

This is how a function (and the associated block) that invokes suspedCoroutine/suspendCancellableCoroutine looks. The first one is not performing anything asynchronous and hence there is no suspension. The second one is causing a suspension for real (it invokes an asynchronous function that will use as callback continuation.resume).


//Path 1: Immediate Completion (No Suspension)
suspend fun quickOperation(): String {
    return suspendCoroutine { cont ->
        // This completes immediately
        cont.resume("immediate result")
        // shouldSuspend() will return false
    }
}

//Path 2: Deferred Completion (Actual Suspension)
suspend fun networkCall(): String {
    return suspendCoroutine { cont ->
        // Register with event loop, but don't complete yet
        eventLoop.registerSocket(socket) { response ->
            cont.resume(response) // This happens later!
        }
        // Block ends without calling cont.resume()
        // shouldSuspend() will return true
    }
}


And this is the "Kotlin pseudocode" for suspendCoroutineUninterceptedOrReturn (as I've said, being an intrinsic there's no Kotlin source for it)


// Conceptual implementation of suspendCoroutine
suspend fun  suspendCoroutineUninterceptedOrReturn(block: (Continuation) -> Unit): T {
    val continuation = getCurrentContinuation()
    
    // Mark continuation as "not completed yet"
    continuation.markAsPending()
    
    block(continuation) // User code runs
    
    // Check if the block completed the continuation synchronously
    if (continuation.isCompleted) {
        // Fast path - no actual suspension needed
        return continuation.getResult()
    } else {
        // Slow path - we need to suspend
        return COROUTINE_SUSPENDED
    }
}

Notice that in the quickOperation() function that invokes suspendCoroutine with a block that returns immediatelly, that block is calling continuation.resume. That feels odd, cause as it's not suspending there's no need to resume a continuation, just set the continuation as completed and return normally. Well, Claude to the rescue, that's exactly what continuation.resume is doing in that particular case.

In practice, continuation.resume() is indeed doing exactly what you're thinking - it's setting the result and marking the continuation as completed, but without triggering any actual resumption logic.

So the same continuation.resume function manages 2 completely different situations:

- Synchronous completion:
Continuation is completed within the suspendCoroutine block
No actual suspension occurs
Result flows directly back through the call stack


- Asynchronous completion (typical case):
Continuation is completed later from a callback/event handler
Actual suspension and resumption occurs
Result triggers coroutine resumption on appropriate dispatcher

Claude can guess that its implementation should look more or less like this:


// Conceptual implementation
class ContinuationImpl {
    private var result: Any? = UNINITIALIZED
    private var isCompleted = false
    
    fun resume(value: T) {
        if (!isCompleted) {
            this.result = Result.success(value)
            this.isCompleted = true
            
            // Key point: if we're in synchronous context,
            // this doesn't trigger async resumption
            if (inSuspendCoroutineBlock) {
                // Just mark as completed, don't dispatch
            } else {
                // Actually dispatch to resume suspended coroutine
                dispatch()
            }
        }
    }
}

An additional point. The suspendCoroutine function (and suspendCancellableCoroutine and suspendCoroutineUninterceptedOrReturn) is marked with the suspend modifier, but indeed, is this necessary? suspendCoroutine is never going to get suspended (no state machine needed for it), depending on what the block does it will return a value or return the CoroutineSuspended marker value. The resuming will start in the continuation corresponding to the caller of suspendCoroutine. So, why is it marked with suspend? Again, Claude to the rescue:

The suspend modifier on suspendCoroutine is purely for access control and type safety - it ensures it's only called from suspend context where the compiler can properly handle the COROUTINE_SUSPENDED return value.

For closing up I'll add a link to a pretty good article on this topic.