Memory Layout and Safety
Relevant source files
This document explains the memory layout of the HandlerTable structure and the safety mechanisms that ensure correct lock-free operation. It covers how function pointers are stored atomically, memory ordering guarantees, and the safety implications of pointer transmutation operations.
For information about the specific atomic operations used, see Atomic Operations. For general API usage patterns, see API Reference.
Memory Layout Overview
The HandlerTable<N> structure has a simple but carefully designed memory layout that enables lock-free concurrent access to event handlers.
HandlerTable Structure Layout
flowchart TD
subgraph MemoryValues["Stored Values"]
Zero["0 = Empty Slot"]
FnPtr["fn() as usizeFunction Pointer"]
end
subgraph HandlerTableStruct["HandlerTable<N> Memory Layout"]
ArrayField["handlers: [AtomicUsize; N]"]
subgraph AtomicArray["AtomicUsize Array"]
Slot0["AtomicUsize[0]8 bytes"]
Slot1["AtomicUsize[1]8 bytes"]
SlotDots["..."]
SlotN["AtomicUsize[N-1]8 bytes"]
end
end
Slot0 --> Zero
Slot1 --> FnPtr
SlotN --> Zero
HandlerTable Memory Characteristics
| Property | Value | Rationale |
|---|---|---|
| Size | N * size_of:: | Fixed compile-time size |
| Alignment | align_of:: | Platform atomic alignment |
| Initialization | All slots = 0 | Zero represents empty slot |
| Mutability | Atomic operations only | Lock-free concurrency |
Sources: src/lib.rs(L14 - L16) src/lib.rs(L20 - L24)
Function Pointer Storage Mechanism
The core safety challenge is storing function pointers (fn()) in atomic integers (AtomicUsize) while maintaining type safety and memory safety.
Pointer Transmutation Process
sequenceDiagram
participant ApplicationCode as "Application Code"
participant HandlerTable as "HandlerTable"
participant AtomicUsizeidx as "AtomicUsize[idx]"
participant RawMemory as "Raw Memory"
Note over ApplicationCode,RawMemory: Registration: fn() → usize
ApplicationCode ->> HandlerTable: "register_handler(idx, handler_fn)"
HandlerTable ->> HandlerTable: "handler as usize"
Note over AtomicUsizeidx,HandlerTable: Transmute fn() to usize
HandlerTable ->> AtomicUsizeidx: "compare_exchange(0, usize_value)"
AtomicUsizeidx ->> RawMemory: "Atomic store"
Note over ApplicationCode,RawMemory: Execution: usize → fn()
ApplicationCode ->> HandlerTable: "handle(idx)"
HandlerTable ->> AtomicUsizeidx: "load(Ordering::Acquire)"
AtomicUsizeidx ->> RawMemory: "Atomic load"
RawMemory -->> HandlerTable: "usize_value"
HandlerTable ->> HandlerTable: "transmute::<usize, fn()>(usize_value)"
Note over AtomicUsizeidx,HandlerTable: Transmute usize to fn()
HandlerTable ->> HandlerTable: "handler()"
Note over AtomicUsizeidx,HandlerTable: Call function
Transmutation Safety Requirements
The unsafe transmute operations in the codebase rely on several critical safety invariants:
- Size Compatibility:
size_of::<fn()>() == size_of::<usize>()on the target platform - Representation Compatibility: Function pointers can be safely cast to/from usize
- Lifetime Management: Function pointers remain valid for the lifetime of storage
- Non-zero Constraint: Valid function pointers are never zero (allowing zero as "empty")
Sources: src/lib.rs(L35) src/lib.rs(L48) src/lib.rs(L64)
Memory Ordering Guarantees
The HandlerTable uses specific memory ordering constraints to ensure correct synchronization without locks.
Ordering Operations Mapping
flowchart TD
subgraph Ordering["Memory Ordering"]
Acquire["Ordering::Acquire"]
Relaxed["Ordering::Relaxed"]
end
subgraph AtomicOps["Atomic Operations"]
CAS["compare_exchange()"]
Load["load()"]
Swap["swap()"]
end
subgraph Operations["HandlerTable Operations"]
Register["register_handler()"]
Handle["handle()"]
Unregister["unregister_handler()"]
end
CAS --> Acquire
CAS --> Relaxed
Handle --> Load
Load --> Acquire
Register --> CAS
Swap --> Acquire
Unregister --> Swap
Memory Ordering Analysis
| Operation | Success Ordering | Failure Ordering | Purpose |
|---|---|---|---|
| compare_exchange | Acquire | Relaxed | Synchronize handler installation |
| load | Acquire | N/A | Ensure handler visibility |
| swap | Acquire | N/A | Synchronize handler removal |
The Acquire ordering ensures that:
- Handler installations are visible to all threads before the operation completes
- Handler loads see the most recent installed handler
- Handler removals synchronize with ongoing executions
Sources: src/lib.rs(L35) src/lib.rs(L46) src/lib.rs(L62)
Zero Value Semantics
The choice of zero as the "empty slot" marker has important memory safety implications.
Zero Representation Safety
flowchart TD
subgraph SafetyChecks["Runtime Safety Checks"]
CheckNonZero["if handler != 0"]
ReturnSome["Some(handler)"]
ReturnNone["None"]
end
subgraph ZeroSemantics["Zero Value Semantics"]
EmptySlot["0 = Empty Slot"]
ValidPtr["Non-zero = Valid fn()"]
NullPtr["Null Function Pointer = 0"]
end
subgraph Initialization["Array Initialization"]
ConstNew["const fn new()"]
AtomicNew["AtomicUsize::new(0)"]
ArrayInit["[const { ... }; N]"]
end
ArrayInit --> EmptySlot
AtomicNew --> ArrayInit
CheckNonZero --> ReturnNone
CheckNonZero --> ReturnSome
ConstNew --> AtomicNew
EmptySlot --> CheckNonZero
EmptySlot --> ValidPtr
ValidPtr --> NullPtr
Zero Value Safety Properties
- Platform Guarantee: On all supported platforms, null function pointers have value 0
- Initialization Safety:
AtomicUsize::new(0)is const and safe - Detection Safety: Zero checks reliably distinguish empty from occupied slots
- Transmute Safety: Zero values are never transmuted to function pointers
Sources: src/lib.rs(L22) src/lib.rs(L47 - L51) src/lib.rs(L63 - L68)
Compile-time Safety Constraints
The generic constant N parameter provides compile-time memory safety guarantees.
Fixed-Size Array Benefits
flowchart TD
subgraph MemoryLayout["Memory Layout Properties"]
Contiguous["Contiguous Memory"]
CacheEfficient["Cache-Friendly Access"]
NoFragmentation["No Heap Fragmentation"]
FixedSize["Fixed Memory Footprint"]
end
subgraph RuntimeSafety["Runtime Bounds Safety"]
BoundsCheck["if idx >= N"]
EarlyReturn["return false/None"]
SafeAccess["handlers[idx]"]
end
subgraph CompileTime["Compile-time Guarantees"]
ConstN["const N: usize"]
FixedArray["[AtomicUsize; N]"]
StackAlloc["Stack Allocation"]
NoHeap["No Heap Required"]
end
BoundsCheck --> EarlyReturn
BoundsCheck --> SafeAccess
ConstN --> FixedArray
Contiguous --> CacheEfficient
FixedArray --> BoundsCheck
FixedArray --> FixedSize
FixedArray --> StackAlloc
NoHeap --> NoFragmentation
StackAlloc --> Contiguous
StackAlloc --> NoHeap
Compile-time Safety Benefits
- No Dynamic Allocation: Array size known at compile time, enabling
no_stdcompatibility - Bounds Safety: Array access bounds are checked explicitly before atomic operations
- Memory Predictability: Fixed memory footprint enables real-time system usage
- Cache Locality: Contiguous array layout optimizes memory access patterns
Sources: src/lib.rs(L14 - L16) src/lib.rs(L31 - L33) src/lib.rs(L43 - L45) src/lib.rs(L59 - L61)
Unsafe Code Safety Analysis
The HandlerTable contains two unsafe blocks that require careful safety analysis.
Transmute Safety Invariants
flowchart TD
subgraph Verification["Safety Verification"]
CompileTime["Compile-time size check"]
RuntimeCheck["Runtime zero check"]
SourceGuarantee["Only valid fn() stored"]
StaticLifetime["Static function lifetime"]
end
subgraph SafetyConditions["Required Safety Conditions"]
SizeMatch["sizeof(fn()) == sizeof(usize)"]
ValidPtr["Stored value was valid fn()"]
NonZero["Value != 0 before transmute"]
Lifetime["Function still valid"]
end
subgraph UnsafeBlocks["Unsafe Code Locations"]
Unregister["unregister_handler() line 48"]
Handle["handle() line 64"]
end
Lifetime --> StaticLifetime
NonZero --> RuntimeCheck
SizeMatch --> CompileTime
ValidPtr --> SourceGuarantee
Safety Argument Summary
- Size Safety: Function pointers and
usizehave identical size on all target platforms - Value Safety: Only valid function pointers (converted via
as usize) are stored - Zero Safety: Runtime checks prevent transmuting zero values
- Lifetime Safety: Event handlers typically have static lifetime in kernel contexts
The unsafe transmute operations are sound because they only reverse a previous safe cast operation under controlled conditions.
Sources: src/lib.rs(L48) src/lib.rs(L64)