Stackless fibers

Likely limitations/issues:

  • Traits. Their memory usage can be calculated based on all classes that implements them.

  • Recursive calls. New alloc per call, or possibly do not allow suspend in recursive calls.

Fiber.run() and main() are task entry points. They have one state each. Each state contains all data needed from entry point to all suspend calls.

Stackful fibers are probably faster than multi level state machine stackless fibers, and probably similar speed to single level state machine stackless fibers.

Stackless fibers often use less memory than stackful fibers. It’s certainly true for fibers that use very little stack (much less than a memory page).

Another option is to calculate the maximum stack usage of each fiber at compile time and spawn them with stacks of that size. This requires a user space fiber scheduler.

func bar(v: i64) -> i64:
    for _ in range(10):
        v += random()

    return v

func fum():
    v = 0
    # Call fiber function.
    sleep(2.0)
    v += 1

func foo(x: i64, y: string) -> string:
    a = x + y
    b = 0

    for ch in y:
        b += bar(a + i64(ch))

    # Call fiber function.
    sleep(1.0)
    fum()

    return str(b)

func main():
    res = foo()

    raise Exception(res)

Multi level state machine:

func bar(v: i64) -> i64:
    for _ in range(10):
        v += random()

    return v

class Fum:
    _state: i64
    v: i64
    sleep: Sleep

    func next(self) -> bool:
        while True:
            match self._state:
                case 0:
                    self.v = 0
                    self.sleep = Sleep(2.0)
                    self._state = 1
                case 1:
                    if self.sleep.next():
                        self._state = 2
                    else:
                        return False
                case 2:
                    self.v += 1
                case 3:
                    return True

class Foo:
    _state: i64
    b: i64
    sleep: Sleep?
    fum: Fum?

    func next(self) -> bool:
        while True:
            match self._state:
                case 0:
                    a = x + y
                    self.b = 0

                    for ch in y:
                        self.b += bar(a + i64(ch))

                    self.sleep = Sleep(1.0)
                    self._state = 1
                case 1:
                    if self.sleep.next():
                        self.sleep = None
                        self.fum = Fum()
                        self._state = 2
                    else:
                        return False
                case 2:
                    if self.fum.next():
                        self.fum = None
                        self._state = 3
                    else:
                        return False
                case 3:
                    self.result = str(self.b)
                    self._state = 4
                case 4:
                    return True

func main():
    foo = Foo()

    while True:
        if foo.next():
            res = foo.result
            break

    raise Exception(res)

Single level state machine:

enum TaskState:
    Init
    FooSleep
    FooFumSleep
    Finished

class Task:
    state: TaskState
    b: i64
    v: i64

    func __init__(self):
        self.state = TaskState.Init

func part_1(task: Task):
    a = x + y
    task.b = 0

    for ch in y:
        task.b += bar(a + i64(ch))

    do_sleep(1.0)
    task.state = TaskState.PartSleep

func part_2(task: Task):
    task.v = 0
    do_sleep(2.0)
    task.state = TaskState.PartFumSleep

func part_3(task: Task) -> string:
    task.v += 1
    task.state = TaskState.Finished

    return str(task.b)

func main():
    task = Task()
    res = None

    while True:
        match task.state:
            case TaskState.Init:
                part_1()
            case TaskState.PartSleep:
                part_2()
            case TaskState.PartFumSleep:
                res = part_3()
            case TaskState.Finished:
                break

        reschedule()

    raise Exception(res)