IPC Fastpath
The IPC fastpath is an optimized dispatch path for the two most common IPC patterns: Call (syscall 2) and ReplyRecv (syscall 3).
The assembly syscall entry point checks the syscall number and, for Call or ReplyRecv, jumps directly to a Rust fastpath function before the full ABI translation runs.
If the fastpath cannot handle the operation, it returns a status code that causes the assembly stub to fall through to the normal slowpath.
Scope
The fastpath handles only simple, short messages between two threads on the same CPU with no capability transfer. All other cases take the slowpath, which supports the full IPC feature set.
Fastpath |
Call, ReplyRecv, ReplyRecvAny |
|---|---|
Slowpath |
All 31 syscalls including Send, Recv, NBSend, timed variants, multi-endpoint variants, and any Call/ReplyRecv that fails fastpath eligibility |
Eligibility Conditions
The Call fastpath handles the operation only when all of the following hold:
-
extra_caps == 0— no capabilities to transfer. -
Message
length ⇐ 4— the entire message fits in registers (MR0-MR3). -
The resolved capability is an
EndpointwithCALLright. -
A receiver is already waiting in
RecvBlockedstate on the endpoint. -
The receiver is locally stable on the current CPU (not migrating, not in another CPU’s ready queue).
-
The receiver has a valid VSpace root and kernel stack.
If any condition fails, the fastpath returns FastpathResult::slowpath() and the assembly stub falls through to the full dispatch path.
The ReplyRecv fastpath has analogous conditions for the reply and the subsequent receive.
Register ABI
On x86_64, the fastpath functions receive arguments directly from the syscall registers:
| Register | Argument | Content |
|---|---|---|
|
|
Capability address (endpoint slot) |
|
|
Packed message info word |
|
|
Message register 0 |
|
|
Message register 1 |
|
|
Message register 2 |
|
|
Message register 3 |
Return value (FastpathResult):
#[repr(C)]
pub struct FastpathResult {
pub status: u64, // 1 = handled, 0 = fall through to slowpath
pub value: u64, // badge (for ReplyRecv), 0 (for Call)
}
The assembly stub checks status: if 1, it returns directly to userspace with the result.
If 0, it proceeds to the full syscall_handle_rust() entry point.
User RSP is saved on the per-thread kernel stack, not in per-CPU %gs:16.
The per-CPU slot is shared state overwritten by other threads' syscalls and is not used for RSP preservation in the fastpath.
|
Lock Protocol
The fastpath acquires locks in the same order as the slowpath to avoid deadlock:
-
CAP_LOCK— capability copy-to-stack, then released. The endpoint capability is copied to a stack-local variable underCAP_LOCK; the lock is released immediately after. This prevents torn reads without holdingCAP_LOCKacross the IPC operation. -
SCHED_IPC_LOCK— covers both the IPC operation and scheduler enqueue/context switch. Acquired afterCAP_LOCKis released. -
scheduler.lock_state— per-CPU scheduler state (acquired underSCHED_IPC_LOCK). Also referred to assched.lock_cpuin some source comments.
The fastpath never holds CAP_LOCK and SCHED_IPC_LOCK simultaneously.
Message Transfer
In the fastpath, message transfer is register-to-register:
-
MR0-MR3 are written directly from the sender’s registers into the receiver’s register save area.
-
No IPC buffer access is required (since
length ⇐ 4andextra_caps == 0). -
The badge is delivered through the receiver’s badge register.
This avoids the memory accesses that the slowpath requires for IPC buffer overflow and capability transfer.
Multi-Endpoint Fastpath
ReplyRecvAny has a fastpath variant that handles the common case where only one of the registered endpoints has a ready sender.
The same eligibility conditions apply: short message, no cap transfer, receiver locally stable.
Performance Characteristics
The fastpath eliminates:
-
Full ABI register save/restore (only the necessary registers are saved)
-
Message info unpacking and validation (checked inline)
-
IPC buffer resolution and memory copy (registers only)
-
Capability transfer machinery (no caps)
The result is a direct sender-to-receiver thread switch with minimal kernel overhead. This is the critical performance path for microkernel IPC, as every client-server interaction in SaltyOS passes through endpoints.
Bailout Conditions and Edge Cases
Receiver Is Fault-Blocked
If the receiver is in FaultBlocked state (waiting for a fault handler to reply), the fastpath bails to slowpath.
A faulted thread cannot act as a ready receiver for an IPC rendezvous.
Slowpath Fallback Is Transparent
When the fastpath bails (returns FastpathResult::slowpath()), the assembly stub falls through to syscall_handle_rust(), which handles the operation identically to a direct slowpath entry.
The caller never knows which path executed — the ABI contract is identical.
Capability Lookup Under CAP_LOCK
The fastpath copies the endpoint capability to a stack-local variable under CAP_LOCK, then releases the lock.
If another CPU concurrently deletes or modifies the capability between the copy and the endpoint lock acquisition, the stack-local copy remains valid — it was copied by value.
The endpoint object itself is refcounted and cannot be freed while the capability exists.
Receiver Migrated Between CPUs
If the receiver’s last_cpu differs from the current CPU, fastpath_waiter_is_local_stable() returns false and the fastpath bails.
This prevents the fastpath from switching to a thread whose context was last saved on a different CPU and may not yet be fully flushed.
Related Pages
-
Endpoints — full IPC operations and slowpath semantics
-
Notifications — async signaling (used alongside endpoints)
-
Architecture — syscall entry assembly and lock ordering