Skip to main content

ax_task/runtime/
interface.rs

1//! Operating-system capability table consumed by ax-task.
2
3use trait_ffi::def_extern_trait;
4
5use crate::{
6    runtime::{
7        RuntimeHandleResult, RuntimeStatus, TaskSystemHandle,
8        cpu::{
9            CpuRemoteHandle, CurrentCpuOwnerHandles, IrqGuardToken, LocalIrqState,
10            PreemptGuardToken, RqClockSample, RuntimeCpuId, SchedulerDeadlineUpdate,
11            SchedulerRuntimeDeadline,
12        },
13        resource::{
14            AddressSpaceDestroyOutcome, AddressSpaceHandle, AddressSpaceMembarrierState,
15            AddressSpaceReclaimArmOutcome, ExecutionContextHandle, KernelContextRequest,
16            MembarrierRegistration, MembarrierRegistrationPhase, RuntimeMembarrierAction,
17            StackHandle, StackRequest, TlsHandle, UserContextRequest,
18        },
19        switch::{
20            ContextThreadBinding, CurrentThreadPublication, RuntimeScheduleOrigin,
21            RuntimeSchedulerEntry, RuntimeSchedulerFrameEnterResult, RuntimeSchedulerReturn,
22            RuntimeSwitchPlan, SchedSwitchRecord, ThreadIdentityV1,
23        },
24    },
25    time::MonotonicInstant,
26};
27
28/// OS capabilities needed by the scheduling core.
29///
30/// Implementations must keep task-system and CPU-local handles valid until
31/// shutdown. All IRQ-path methods must be allocation-free and non-blocking.
32#[def_extern_trait(mod_path = "runtime", abi = "rust")]
33pub trait TaskRuntime {
34    /// Returns the runtime-owned task-system handle, or `NONE` before setup.
35    ///
36    /// # Safety
37    ///
38    /// A non-`NONE` result must identify a pinned [`crate::runtime::TaskSystem`] that
39    /// remains live until shutdown. The linked runtime provider is the trust
40    /// root for this raw handle; callers cannot validate it dynamically.
41    unsafe fn task_system_handle() -> TaskSystemHandle;
42
43    /// Captures the complete pinned scheduler capability for the calling CPU.
44    ///
45    /// This is a CPU-owned capability, not a migration-stable task handle. The
46    /// caller must retain an IRQ guard or scheduler-frame baton from before this
47    /// query until every dereference of the returned object has completed.
48    ///
49    /// # Safety
50    ///
51    /// The returned identity, owner-only [`crate::runtime::cpu::CpuLocal`] handle and
52    /// Arc-backed [`crate::runtime::cpu::CpuRemote`] handle must all describe the same calling
53    /// CPU and remain live until shutdown. The local address must originate
54    /// from the allocation's mutable owner capability, not from a shared
55    /// `CpuLocal` borrow. Before reconstructing a reference, the caller must
56    /// claim the returned remote endpoint's owner gate and retain both that
57    /// claim and its CPU pin for the complete derived-borrow lifetime.
58    unsafe fn current_cpu_owner_handles() -> CurrentCpuOwnerHandles;
59
60    /// Returns the Arc-backed [`crate::runtime::cpu::CpuRemote`] endpoint for the calling CPU.
61    ///
62    /// This is the scheduler-adjacent current-CPU fast path. Unlike
63    /// [`Self::cpu_remote_handle`], it must not derive a CPU identifier and
64    /// resolve that identifier through the global task-system registry.
65    ///
66    /// # Safety
67    ///
68    /// The caller must prevent migration until it has finished every read
69    /// through the returned endpoint. A non-`NONE` result must identify the
70    /// calling CPU's Arc-backed [`crate::runtime::cpu::CpuRemote`] and remain live until
71    /// shutdown. It must not identify a [`crate::runtime::cpu::CpuLocal`] or any other
72    /// allocation.
73    unsafe fn current_cpu_remote_handle() -> CpuRemoteHandle;
74
75    /// Returns the generation-bearing scheduler identity bound to the calling
76    /// execution context.
77    ///
78    /// This is the identity-only equivalent of Linux's direct `current`
79    /// pointer for fast paths that do not need an owner reference. Providers
80    /// must read the task-owned runtime context selected by the architecture
81    /// current-thread register. [`ThreadIdentityV1::NONE`] denotes an unbound
82    /// bootstrap context.
83    ///
84    /// A bound result must equal the identity in
85    /// [`Self::current_thread_publication`] and remain immutable for the
86    /// complete lifetime of that runtime context.
87    fn current_thread_identity() -> ThreadIdentityV1;
88
89    /// Returns the scheduler publication bound to the calling execution context.
90    ///
91    /// This is the local equivalent of Linux's direct `current` task pointer.
92    /// Providers must read the task-owned runtime context selected by the
93    /// architecture current-thread register; they must not resolve the local
94    /// publication through a remote runqueue endpoint.
95    /// [`CurrentThreadPublication::NONE`] denotes an unbound bootstrap context.
96    ///
97    /// A bound result must match the scheduler core retained by the current
98    /// task and remain immutable for the complete lifetime of that runtime
99    /// context. Preemption and migration must not change this task identity.
100    fn current_thread_publication() -> CurrentThreadPublication;
101
102    /// Tests the current execution context's advisory preemption-pending state.
103    ///
104    /// This is the runtime equivalent of Linux's `need_resched()` safe-point
105    /// query. It must read the architecture-selected current state without
106    /// disabling preemption or claiming scheduler work. A `false` result is
107    /// only a snapshot.
108    fn current_preemption_pending() -> bool;
109
110    /// Returns the Arc-backed [`crate::runtime::cpu::CpuRemote`] endpoint for `cpu`.
111    ///
112    /// Unlike [`Self::current_cpu_local_handle`], this handle must never point
113    /// at [`crate::runtime::cpu::CpuLocal`]. Remote producers may retain and dereference the
114    /// endpoint without aliasing the owner CPU's mutable runqueue borrow.
115    ///
116    /// # Safety
117    ///
118    /// A non-`NONE` result must identify the Arc-backed [`crate::runtime::cpu::CpuRemote`]
119    /// endpoint for `cpu` and remain live until shutdown. It must not identify
120    /// a [`crate::runtime::cpu::CpuLocal`] or any other allocation.
121    unsafe fn cpu_remote_handle(cpu: RuntimeCpuId) -> CpuRemoteHandle;
122
123    /// Returns the calling CPU's logical identifier under an existing pin.
124    ///
125    /// # Safety
126    ///
127    /// The caller must prevent migration until it has finished the local
128    /// operation associated with the returned identity.
129    unsafe fn current_cpu_id() -> RuntimeCpuId;
130
131    /// Prepares one owner CPU's runtime facilities for scheduler publication.
132    ///
133    /// The caller holds local IRQ exclusion and has validated that the CPU is
134    /// currently offline. The implementation must prepare every CPU-local
135    /// wake source needed by the scheduler, including its physical
136    /// clockevent, before returning success. It must not allocate, block,
137    /// invoke callbacks, or re-enter ax-task. Failure must leave the runtime
138    /// offline and retryable.
139    fn prepare_cpu_online(cpu: RuntimeCpuId) -> RuntimeStatus;
140
141    /// Stops one owner CPU's runtime facilities before final offline publication.
142    ///
143    /// The scheduler has already closed remote admission and proved the CPU
144    /// quiescent, but still reports it online while this hook runs. The
145    /// implementation must stop every CPU-local wake source, including its
146    /// physical clockevent, before returning success. Failure must leave the
147    /// runtime retryable. The hook must not allocate, block, invoke callbacks,
148    /// or re-enter ax-task.
149    fn prepare_cpu_offline(cpu: RuntimeCpuId) -> RuntimeStatus;
150
151    /// Saves the raw local-interrupt state and disables local interrupts.
152    ///
153    /// This operation does not enter the scheduler's nested IRQ-guard owner
154    /// scope. Synchronization guards need that narrower capability so an IRQ
155    /// return can still own and consume its explicit preemption depth.
156    fn local_irq_save_and_disable() -> LocalIrqState;
157
158    /// Restores a raw local-interrupt state.
159    ///
160    /// # Safety
161    ///
162    /// `state` must have been returned by
163    /// [`Self::local_irq_save_and_disable`] on this CPU and must be restored
164    /// exactly once in properly nested order.
165    unsafe fn local_irq_restore(state: LocalIrqState);
166
167    /// Saves raw interrupt state, disables local IRQs and enters nested guards.
168    /// A hard interrupt that already owns the runtime's IRQ-return pin may
169    /// return [`IrqGuardToken::NONE`]; nested users then borrow that single
170    /// outer CPU owner instead of creating per-handler guard transactions.
171    fn irq_guard_enter() -> IrqGuardToken;
172
173    /// Leaves one nested IRQ guard and restores the outer raw state if needed.
174    ///
175    /// # Safety
176    ///
177    /// `token` must have been returned by `irq_guard_enter` on this CPU and
178    /// must be exited exactly once. [`IrqGuardToken::NONE`] is a no-op borrowed
179    /// owner. Non-empty tokens may be exited in non-LIFO order.
180    unsafe fn irq_guard_exit(token: IrqGuardToken);
181
182    /// Prevents the current task context from being preempted or migrated.
183    ///
184    /// This capability does not disable hardware interrupts. It is valid only
185    /// in task context or inside an active scheduler frame; scheduler state
186    /// shared with hard-IRQ producers uses the separate IRQ-safe lock domain.
187    ///
188    /// When an enclosing scheduler frame or runtime IRQ guard already owns the
189    /// CPU for the complete lock scope, the runtime returns
190    /// [`PreemptGuardToken::NONE`]. The matching lock guard then releases only
191    /// its raw lock; it must not manufacture another ordinary preemption depth
192    /// inside the existing owner transaction.
193    fn preempt_guard_enter() -> PreemptGuardToken;
194
195    /// Leaves one nested task-preemption guard.
196    ///
197    /// The final exit may enter the scheduler when work is pending. When the
198    /// caller already owns a scheduler frame, it must consume only this nested
199    /// depth and preserve the scheduler baton and raw IRQ state.
200    ///
201    /// # Safety
202    ///
203    /// A non-`NONE` `token` must have been returned by
204    /// `preempt_guard_enter` on this task execution context and must be exited
205    /// exactly once. Tokens may be exited in non-LIFO order.
206    unsafe fn preempt_guard_exit(token: PreemptGuardToken);
207
208    /// Leaves one nested task-preemption guard at a hard-IRQ return boundary.
209    ///
210    /// Unlike [`Self::preempt_guard_exit`], the final exit may enter the
211    /// scheduler while hardware IRQs remain disabled and must return with IRQs
212    /// disabled for the architecture exception epilogue.
213    ///
214    /// # Safety
215    ///
216    /// A non-`NONE` `token` must have been returned by
217    /// [`Self::preempt_guard_enter`] on this task execution context and must be
218    /// exited exactly once.
219    unsafe fn preempt_guard_exit_irq_return(token: PreemptGuardToken);
220
221    /// Publishes entry into the runtime's hard-interrupt lifecycle.
222    fn hardirq_enter();
223
224    /// Publishes exit from the runtime's hard-interrupt lifecycle.
225    fn hardirq_exit();
226
227    /// Publishes sticky scheduler work to the current CPU's architecture
228    /// preemption state and reports whether a local safe point makes a self-IPI
229    /// unnecessary.
230    ///
231    /// The caller owns an IRQ guard and has already published the scheduler
232    /// payload. Before returning, the runtime must set the architecture-owned
233    /// `need_resched` state observed by preemption and IRQ return. It may return
234    /// `true` only when that state is guaranteed to reach the scheduler before
235    /// the CPU can sleep: through hard-IRQ return, an active scheduler/preemption
236    /// guard, or atomic conversion of the final task-context IRQ guard into a
237    /// scheduler baton.
238    fn publish_local_scheduler_work() -> bool;
239
240    /// Withdraws the outgoing runtime context's CPU binding after raw switch.
241    ///
242    /// The incoming context calls this exactly once while local IRQs remain
243    /// disabled and before the scheduler clears the outgoing thread's
244    /// `on_cpu` publication. The implementation must not allocate, block,
245    /// invoke callbacks, consume the scheduler baton, or re-enter ax-task. It
246    /// returns the deferred resource-release edge. CPU-time accounting remains
247    /// owned by the runqueue clock and must not open a second time domain in
248    /// the architecture switch tail.
249    /// Any failure is an unrecoverable runtime invariant: the raw switch has
250    /// already committed, so there is no compatibility retry path.
251    fn finish_context_switch_tail() -> bool;
252
253    /// Consumes the CPU-local scheduler switch baton on a fresh context.
254    ///
255    /// The baton is not an [`IrqGuardToken`] and never belongs to a task. A
256    /// resumed scheduler frame consumes the current CPU's baton after the raw
257    /// switch returns; a fresh trampoline calls this hook exactly once after
258    /// completing the switch tail.
259    fn finish_initial_context_switch();
260
261    /// Enters the current CPU's exact scheduler switch phase.
262    ///
263    /// The runtime validates `entry`, disables hardware IRQs, and atomically
264    /// creates one CPU-local baton. For [`RuntimeSchedulerEntry::PreemptExit`]
265    /// and [`RuntimeSchedulerEntry::IrqReturn`], it must transform the exact
266    /// final lock-preemption depth into the scheduler depth. For
267    /// [`RuntimeSchedulerEntry::IrqGuardExit`], it must instead transform the
268    /// final task-context IRQ-publication depth. Neither path may expose a
269    /// fully preemptible intermediate state. The runtime must not save this
270    /// phase in an execution context or migrate ordinary IRQ tokens with tasks.
271    /// [`RuntimeSchedulerEntry::IrqReturnContinuation`] must reproduce Linux's
272    /// IRQ-return pass boundary: establish one preemption depth, enable local
273    /// IRQs without a live scheduler baton, disable them again, then convert
274    /// that exact depth into the next scheduler baton. A successful result
275    /// must carry a non-empty task-system capability; a rejected entry returns
276    /// the unsafe-context sentinel without live capabilities.
277    fn scheduler_frame_guard_enter(
278        origin: RuntimeScheduleOrigin,
279        entry: RuntimeSchedulerEntry,
280    ) -> RuntimeSchedulerFrameEnterResult;
281
282    /// Consumes the current CPU's scheduler switch baton after switch tail.
283    ///
284    /// This hook restores task-context hardware IRQ state and must not schedule
285    /// recursively. It returns `true` only when deferred callbacks may run with
286    /// IRQs enabled and every ordinary guard clear.
287    fn scheduler_frame_guard_exit(
288        return_to: RuntimeSchedulerReturn,
289        needs_reschedule: bool,
290    ) -> bool;
291
292    /// Returns whether execution is currently inside a hard interrupt.
293    fn in_hard_irq() -> bool;
294
295    /// Validates an entry before it publishes task state or creates a baton.
296    ///
297    /// This is the runtime equivalent of Linux `might_sleep()` plus the final
298    /// scheduler-entry context check. It must return [`RuntimeStatus::UnsafeContext`]
299    /// while any ordinary IRQ/preemption guard is live or hardware execution is
300    /// still in hard IRQ context.
301    fn validate_schedule_context(origin: RuntimeScheduleOrigin) -> RuntimeStatus;
302
303    /// Validates one owner-CPU scheduler-state access.
304    ///
305    /// This is the runtime equivalent of Linux's `lockdep_assert_rq_held()`.
306    /// It must return [`RuntimeStatus::Success`] only while the current CPU is
307    /// pinned by an ordinary IRQ guard or owns an active scheduler baton.
308    /// Unlike [`Self::validate_schedule_context`], a completely unguarded task
309    /// context is invalid here: an interrupt-return scheduler entry could
310    /// otherwise re-enter over a live mutable [`crate::runtime::cpu::CpuLocal`] borrow.
311    fn validate_owner_cpu_context() -> RuntimeStatus;
312
313    /// Returns one sample from the finite monotonic `ktime` domain.
314    fn monotonic_now() -> MonotonicInstant;
315
316    /// Returns one coherent source sample for the current owner runqueue.
317    ///
318    /// Runqueue mutation is owner-only: remote operations publish into the
319    /// owner inbox instead of locking another CPU's runqueue. Like Linux
320    /// `update_rq_clock()` on the local rq, the runtime therefore samples the
321    /// pinned current CPU and must not repeat a remote CPU lookup.
322    /// Scheduler absolute values must never be compared directly with
323    /// monotonic deadlines.
324    fn rq_clock_sample() -> RqClockSample;
325
326    /// Commits the current CPU's complete scheduler-deadline state.
327    ///
328    /// The runtime owns the physical clockevent. It must ignore generations
329    /// older than the most recently accepted update and merge the accepted
330    /// scheduler deadline with non-scheduler sources before programming hardware. This
331    /// hook is callable from ordinary task context, so the runtime must hold
332    /// local IRQ exclusion across both state publication and hardware
333    /// programming instead of relying on an implicit caller-side guard.
334    ///
335    /// This is an infallible ownership boundary, like Linux's hrtimer-to-
336    /// clockevent rearm path. A runtime must absorb an expired hardware
337    /// deadline by clamping it to the device's minimum nonzero delta and treat a device that cannot
338    /// retain a wakeup source as a runtime-fatal invariant. Returning a
339    /// recoverable error here would leave the scheduler queue and physical
340    /// clockevent in an unknowable half-committed state.
341    fn publish_scheduler_deadline(update: SchedulerDeadlineUpdate);
342
343    /// Commits the owner CPU's current scheduling-class hrtick.
344    ///
345    /// The caller already owns an IRQ-off scheduler or CPU-owner scope. Unlike
346    /// task and kernel timers, this state is rq-local and must not enter the
347    /// remotely reachable deadline base.
348    fn publish_scheduler_runtime_deadline(update: SchedulerRuntimeDeadline);
349
350    /// Notifies `cpu` after the scheduler has published owner work.
351    ///
352    /// Sticky scheduler flags and owner inbox membership remain owned by
353    /// ax-task. The runtime transports only a coalescible physical edge,
354    /// matching Linux's split between `TIF_NEED_RESCHED`/wake-list state and
355    /// the reschedule or call-function IPI. Success
356    /// means either a fresh edge was sent or an in-flight edge already covers
357    /// this publication; every other status is an unrecoverable lifecycle
358    /// violation.
359    fn notify_scheduler_cpu(cpu: RuntimeCpuId) -> RuntimeStatus;
360
361    /// Restarts the periodic scheduler tick while leaving the idle thread.
362    ///
363    /// Mirrors Linux `tick_nohz_idle_exit()` before `schedule_idle()`: the idle
364    /// loop's own IRQ-off checkpoints cannot cover a reschedule request that
365    /// becomes visible only after IRQs are re-enabled, so the owner schedule
366    /// that switches away from the idle thread owns the restart. The caller is
367    /// the owner CPU inside its scheduler frame with local IRQs disabled. The
368    /// implementation must not allocate, block, invoke callbacks, or re-enter
369    /// ax-task; an already running tick is a no-op.
370    fn idle_exit_restart_scheduler_tick();
371
372    /// Commits one local interrupt wait after the scheduler publishes polling.
373    ///
374    /// The implementation must disable local interrupts, call
375    /// `finish_current_cpu_idle_polling`, and immediately recheck sticky task
376    /// work and physical clockevent state before stopping the periodic tick or
377    /// sleeping. It may use the architecture's atomic IRQ-enable-and-wait
378    /// primitive only when all sources remain idle. Task deadlines stay armed
379    /// while the scheduler tick is stopped. Work published before polling was
380    /// cleared is observed by the final recheck; work published afterwards
381    /// owns a physical interrupt edge. The tick must restart before runnable
382    /// work can leave the idle loop, but may remain stopped across
383    /// non-scheduling IRQs.
384    fn wait_for_interrupt();
385
386    /// Allocates a guarded stack satisfying `request`.
387    ///
388    /// On success, `handle` must be non-zero and uniquely identify a live stack
389    /// accepted by [`Self::deallocate_stack`] until ownership is transferred.
390    fn allocate_stack(request: StackRequest) -> RuntimeHandleResult;
391
392    /// Releases a stack after the reaper proves no context can reference it.
393    ///
394    /// Ownership transfers exactly once. The scheduler has already crossed the
395    /// switch-tail lifetime boundary, so a provider must treat an inability to
396    /// release this handle as a fatal runtime invariant rather than inventing a
397    /// polling retry protocol.
398    fn deallocate_stack(stack: StackHandle);
399
400    /// Allocates TLS for the runtime's fixed kernel-image template.
401    ///
402    /// Template layout, initialization, and alignment belong to the runtime.
403    /// This operation does not allocate arbitrary application TLS.
404    ///
405    /// On success, `handle` must be non-zero and uniquely identify a live TLS
406    /// allocation accepted by [`Self::deallocate_tls`] until ownership moves.
407    fn allocate_kernel_tls() -> RuntimeHandleResult;
408
409    /// Releases a TLS area after its execution context has been destroyed.
410    ///
411    /// Ownership transfers exactly once after no context can reference the
412    /// allocation. Failure is a fatal runtime invariant.
413    fn deallocate_tls(tls: TlsHandle);
414
415    /// Creates a kernel execution context.
416    ///
417    /// On success, `handle` must be non-zero and uniquely identify a live
418    /// context accepted by [`Self::destroy_context`].
419    fn create_kernel_context(request: KernelContextRequest) -> RuntimeHandleResult;
420
421    /// Creates a user-capable execution context with a mandatory address space.
422    ///
423    /// On success, `handle` must follow the same ownership contract as
424    /// [`Self::create_kernel_context`].
425    fn create_user_context(request: UserContextRequest) -> RuntimeHandleResult;
426
427    /// Binds a created context to its final generation-bearing thread ID.
428    ///
429    /// The runtime must validate the context handle and install the association
430    /// atomically. A failed call must leave the context unbound so construction
431    /// can destroy it. This hook runs under the task registry's preempt-only
432    /// lock; it must not allocate, block, invoke callbacks, or re-enter
433    /// ax-task.
434    ///
435    /// Providers without execution contexts still export this capability and
436    /// return `Unsupported`, keeping trait-FFI symbol completeness explicit.
437    fn bind_context_thread(binding: ContextThreadBinding) -> RuntimeStatus;
438
439    /// Destroys an execution context that cannot be scheduled again.
440    ///
441    /// The registry makes a record reclaimable only after switch tail clears
442    /// physical CPU ownership. Destruction is therefore a single, infallible
443    /// ownership transfer; a provider must report an impossible live-context
444    /// state through its own fatal invariant path rather than return `Busy`.
445    fn destroy_context(context: ExecutionContextHandle);
446
447    /// Releases an address-space object after no CPU retains it as active mm.
448    ///
449    /// [`AddressSpaceDestroyOutcome::Active`] leaves the object live and
450    /// accepted by a later retry. This task-context operation may drop the OS
451    /// ownership lease; it is never invoked from the IRQ-off context-switch
452    /// path. The runtime must treat an invalid handle as a fatal provider
453    /// invariant rather than expose a compatibility status.
454    fn destroy_address_space(address_space: AddressSpaceHandle) -> AddressSpaceDestroyOutcome;
455
456    /// Arms one deferred retry after destruction observed an active CPU lease.
457    ///
458    /// The token is already queued in ax-task before this call. The runtime
459    /// returns [`AddressSpaceReclaimArmOutcome::Ready`] if no CPU lease remains,
460    /// or records an allocation-free notification obligation and returns
461    /// [`AddressSpaceReclaimArmOutcome::Armed`]. The CPU that drops the last
462    /// lease must then call [`crate::runtime::resource::notify_address_space_reclaim`]. An invalid
463    /// handle is a fatal provider invariant.
464    fn arm_address_space_reclaim(
465        address_space: AddressSpaceHandle,
466    ) -> AddressSpaceReclaimArmOutcome;
467
468    /// Loads the shared `mm` identity and membarrier registration state.
469    ///
470    /// This operation runs while rq locks or local IRQ exclusion may be held.
471    /// It must be a fixed, allocation-free atomic lookup and must not acquire
472    /// an OS lock or re-enter ax-task. An invalid handle is a fatal provider
473    /// invariant.
474    fn address_space_membarrier_state(
475        address_space: AddressSpaceHandle,
476    ) -> AddressSpaceMembarrierState;
477
478    /// Advances one irreversible per-`mm` membarrier registration phase.
479    ///
480    /// `Begin` publishes the requested bit before ax-task inspects runqueues;
481    /// `Complete` publishes the ready bit only after synchronous target-rq
482    /// refresh. The operation must be allocation-free and atomic.
483    fn update_address_space_membarrier_state(
484        address_space: AddressSpaceHandle,
485        registration: MembarrierRegistration,
486        phase: MembarrierRegistrationPhase,
487    ) -> AddressSpaceMembarrierState;
488
489    /// Executes one bounded membarrier action synchronously on `cpu`.
490    ///
491    /// Providers must not return success until the target callback completes.
492    /// The remote callback runs in hard-IRQ context and may only execute the
493    /// selected full barrier or the ax-task rq refresh entry; it must not
494    /// allocate, sleep, or invoke arbitrary OS callbacks.
495    fn synchronize_membarrier_cpu(
496        cpu: RuntimeCpuId,
497        action: RuntimeMembarrierAction,
498    ) -> RuntimeStatus;
499
500    /// Consumes one committed scheduler-switch transaction with local
501    /// interrupts disabled.
502    ///
503    /// # Safety
504    ///
505    /// Both execution-context handles and every non-empty address-space handle
506    /// in `plan` must identify live runtime objects. The caller must have
507    /// committed scheduler state and released runqueue locks. The provider
508    /// must validate and prepare both transitions before committing either one,
509    /// then consume the plan exactly once.
510    unsafe fn switch_context(plan: RuntimeSwitchPlan);
511
512    /// Flushes the current address space's local translation cache.
513    fn flush_tlb_local(start: usize, size: usize);
514
515    /// Captures a context-switch trace record without allocation or rq reentry.
516    ///
517    /// The outgoing rq lock may remain held. An optional returned notification
518    /// runs exactly once in incoming switch completion, after scheduler locks
519    /// and CPU-local borrows are released, with local IRQs still disabled.
520    fn trace_sched_switch(record: SchedSwitchRecord) -> Option<fn()>;
521
522    /// Writes directly to the runtime's emergency console without taking an
523    /// OS lock or re-entering the scheduler.
524    fn emergency_console_write(message: &str);
525
526    /// Reports an unrecoverable scheduler invariant and terminates execution.
527    fn fatal_invariant(code: u32, argument: usize) -> !;
528}