Skip to content

Latest commit

 

History

History

runtime

Foolang C Runtime

This is the support runtime code generated by Foolang's (next) C backend.

Goals & Limitations:

  • Portability: OS-specific code is expected and acceptable but needs to be isolated into OS-specific files. CPU specific code is not. The self-hosting output from C backend and this runtime will be the future bootstrapping path for new platforms.

  • Clarity and debuggability: since this code is expected to remain in place "forever", cleanliness and ease of working with it is at premium.

  • Full featured: this will be the first incarnation of Foolang supporting the entire planned feature set, including N:M actor model with work stealing, and delimited multi-shot continuations.

  • Decent performance: non-allocating loops with only known calls should have at most 1.5 x overhead over C code with similar functionality.

Executor Pool

Executor Pool is a global pool of Executors. It is created on Foolang startup is read-only during system lifetime currently. Normally there is one Executor per CPU core.

Future version of Foolang may support growing and shrinking the pool at runtime, but that is an unnecessary complication at the moment.

Executor Pool has

  • Size: the number of Executors.
  • Executors: pointer to an array of Executors.

Executors

Each Executor has a thread that runs the Executor Loop in a thread, and a FIFO of Actors to run.

The Executor Loop dequeues an Actor from its FIFO, and runs for a timeslice before putting if back in the queue and dequeing the next one. If an Executor has no actors to run it will attempt to steal one from another, or goes to sleep waiting for new Actors to be created it if cannot.

Future version of Foolang will support more complex load balancing between Executors, but that is an unnecessary complication at the moment.

An Executor has

  • Id: position of this Executor in the Executor Pool's array.
  • Lock: a mutex protecting access to this Executor's Queue.
  • Queue: a FIFO of ready Actors.
  • Waiting: a FIFO of waiting Actors.

Scheduling an Actor

  1. Executor locks itself

Running an Actor

One tick of a timeslice consists of:

  • Executor fetches the current continuation from top of the actor's stack.
  • Executor calls the current continuation with current stack pointer and the Actor object.
  • The continuation returns the next stack pointer.

This is repeated until the timeslice runs out, or continuation returns NULL as the next stack pointer.

Actors

Actors are units of control flow and work, similar to threads or processes.

Actors never share mutable memory with each other: they have their own private stack, heap, and dynamic variable bindings.

Actors have following states:

  • READY: Ready for execution by an Executor.
  • RUNNING: Currently being exectuted by an Executor.
  • EXITING: Has completed all its work.

Actors have:

  • Stack
  • Stack pointer
  • Base pointer
  • Heap

Actor Stack

The from top down when READY.

  1. Current Continuation. (Function pointer.)
  2. Stack layout word.
  3. Variable amount of data, size and layout determined by the layout word.
  4. Stack layout word.
  5. Etc.

Practically this means that on entry to a continuation the stack reads:

  • Layout for arguments and continuation
    • Arguments
    • Return continuation
  • Layout for previous frame
    • Variables
  • Layout for previous frame arguments and continuation
    • Arguments
    • Return continuation

So calling a return continuation of a method should look like:

  1. Make a temporary copy of the return continuation address.
  2. Make a temporary copy of the return value.
  3. Pop current frame, including arguments.
  4. Push return value.
  5. Push layout word for return value.
  6. Push return continuation.
  7. Return current SP.