How Flyology Runtime extends GNAT tasking.

Flyology changes how a selected task executes. It preserves ordinary Ada semantics for task identity, rendezvous, protected objects, activation, masters, abort, and exceptions.

ROUTING

Tasks use either native or lightweight execution.

Flyology integrates at System.Task_Primitives.Operations, below GNARL, the GNAT tasking runtime layer. Task creation records the designation once. A lightweight task receives a guarded fiber stack managed by an execution group; a native task follows the OS-thread path.

Flyology task routing An Ada task passes through the tasking runtime and task primitives, then follows either the lightweight execution-group lane or the native OS-thread lane. Application · ordinary Ada task Ada task semantics Task-primitives routing designation captured at creation Lightweight execution group fiber + scheduler + poller Native task lane native OS-thread path

The environment task is always native. A live task cannot change its lightweight or native designation, but a lightweight task can migrate at an explicit safe point.

SEPARATION

Polling, scheduling, and switching have separate functions.

Flyology separates these mechanisms to show the portability boundary and to keep scheduler policy in Ada.

Event polling
The poller waits for socket readiness, file completion, a timer deadline, or a cross-thread wake. Darwin implements this wait with kqueue; Linux uses epoll.
Scheduling
The Ada scheduler selects the next runnable fiber from per-priority FIFO buckets and promotes expired per-group deadlines.
Context switching
A small AArch64 or x86-64 routine saves the required ABI register and stack state, then restores another guarded stack.
LIFECYCLE

A suspended call keeps its stack.

Each lightweight task owns a guarded stackful context. When a call waits, Flyology records its deadline and ownership state before switching to the scheduler. The task later resumes in the original call frame.

  1. Ready
  2. Running
  3. Waiting
  4. Ready
  5. Finished
  • Ready to running: the group scheduler dispatches the highest-priority FIFO bucket.
  • Running to waiting: a rendezvous, timer, or I/O operation suspends the fiber.
  • Waiting to ready: task synchronization, a deadline, or descriptor readiness wakes it.
  • Running to migrating: an explicit safe point hands the fully switched-out fiber to another group.

Two loops can never restore one fiber concurrently. The source scheduler performs the migration handoff only after the fiber has completely switched away.

MEMORY

Runtime memory follows task and operation lifetimes.

Flyology does not replace the general Ada heap allocator. It creates fiber stacks, contexts, scheduler state, poller state, and completion records as needed. Each resource remains alive while a task or kernel operation can refer to it.

Fiber stacks
Flyology maps stacks in guarded arenas with at most 64 slots and a 4 MiB target before the final guard. A suspended or migrating task retains its stack, locals, and exception state.
Guarding and reuse
Each usable stack has an inaccessible guard of at least 64 KiB. Before reusing a released slot, Flyology protects it and requests best-effort page discard. Flyology unmaps an arena when it becomes empty.
Kernel operations
A submitted file buffer remains kernel-owned until completion. Cancellation and abort cannot release the buffer or its completion state early.
Runtime teardown
Flyology reaps a finished fiber when the Ada task lifetime permits it. It releases groups, pollers, queues, and contexts after all task masters and controlled library objects complete.

Stack-pool allocation is process-wide and briefly serialized by a mutex during task activation and final reap. Scheduling, polling, I/O, and context switching do not use that mutex.

Flyology.Observability.Stack_Pool reports current arena, stack, usable-byte, and reserved-byte counts. This operation does not start an execution group.

TOPOLOGY

Each group is a scheduling and ownership domain.

An execution group owns one stable loop thread, ready queues, a deadline heap, a descriptor index, and an OS poller. Groups do not steal work. Automatic placement, explicit Ada CPU selection, and migration determine where a task runs.

Two independent execution groups Each group has its own tasks, priority queues, deadline heap, poller, and stable loop thread. Migration crosses between them explicitly. Group 0 stable loop thread ready queues deadline heap descriptor index OS poller fibers Group 1 stable loop thread ready queues deadline heap descriptor index OS poller fibers

Shared group identifiers are 0 .. 127; dedicated identifiers are 128 .. 255. Migration out of a dedicated group consumes its reservation, so call Create_Dedicated again before re-entry.

SOCKET PATH

Readiness resumes the original operation.

A socket call first attempts the nonblocking OS operation. If it would block, the lightweight path registers one-shot readiness and a deadline before switching to the scheduler. When readiness or the deadline wakes the task, the same call retries the operation.

  1. The Ada task calls receive, send, accept, or connect.
  2. The nonblocking syscall reports that it would block.
  3. The scheduler records descriptor direction, generation, and deadline.
  4. kqueue or epoll reports readiness or the deadline expires.
  5. The task resumes, retries the syscall, and returns a value or exception normally.

Exact reads and complete writes can repeat this cycle while preserving one monotonic deadline. Ownership-aware connections also serialize operations and make cancellation and close descriptor-generation-safe.

FILE PATH

Regular files use kernel completion, not worker threads.

Regular files are usually always reported ready by readiness pollers, so lightweight file I/O uses a completion engine instead.

Darwin
POSIX AIO submissions complete through EVFILT_AIO in the group's kqueue.
Linux primary
Each group owns an io_uring engine integrated with its event loop.
Linux fallback
Linux native AIO completion wakes the group through eventfd.
Native callers
Direct positional syscalls block only the native task's OS thread.
RUNTIME BOUNDARY

Target-specific code stays in the C and assembly bridge.

The C and assembly bridge contains target-specific mechanisms. These include Linux syscall numbers, native epoll_event layout, thread placement, virtual-memory operations, test hooks, and ABI register swaps. Scheduler, queue, timeout, backpressure, file-engine, and retry policy remain in Ada.

  • Contexts: AArch64 and x86-64 implementations preserve all ABI-required callee-saved integer, floating-point, stack, frame, and control state.
  • Stacks: guarded mmap arenas detect overflow through the supported task-runtime signal path and surface Ada Storage_Error.
  • Patches: exact compiler source-context patch families fail closed instead of weakening a hunk for an unverified compiler.
  • Finalization: event machinery is one-shot and tears down only after Ada task masters and controlled library objects are complete.
COMPATIBILITY

Native-only programs start no event machinery.

The environment task is native, and undesignated tasks are native by default. Until the first lightweight task activates, Flyology starts no poller, event-loop thread, scheduler context, or fiber stack.

The external-consumer suite tests this contract with native-default and lightweight-default runtimes. In both configurations, an ordinary native-only program remains on the dormant path before and after creating a normal Ada task.