IRQL — Compile-Time IRQL Safety for Windows Kernel Drivers
Compile-time verification of IRQL (Interrupt Request Level) constraints for Windows kernel-mode drivers. IRQL violations become compiler errors — zero runtime cost.
Features
- Compile-time safety — IRQL violations are caught by
rustc, not at runtime - Zero overhead — all checks use the type system; nothing emitted at runtime
- Clear diagnostics — custom
#[diagnostic::on_unimplemented]messages - Ergonomic — one attribute (
#[irql()]) for functions, impl blocks, and trait impls - Function traits — IRQL-safe
IrqlFn,IrqlFnMut,IrqlFnOnce no_std— designed for kernel-mode environments
Installation
[]
= "0.1.4"
Quick start
use ;
The #[irql()] attribute
| Form | Meaning |
|---|---|
#[irql(at = Level)] |
Fixed entry point — known IRQL, no generic added |
#[irql(max = Level)] |
Callable from Level or below (ceiling) |
#[irql(min = A, max = B)] |
Callable in the range [A, B] |
maxis required unless usingat— it defines the IRQL ceiling thatcall_irql!relies on.minis optional — adds a floor constraint (IrqlCanLowerTo).atis mutually exclusive withmin/max.
Works on functions, inherent impl blocks, and trait impl blocks.
IRQL levels
| Value | Type | Description |
|---|---|---|
| 0 | Passive |
Normal thread execution; paged memory OK |
| 1 | Apc |
Asynchronous Procedure Call delivery |
| 2 | Dispatch |
DPC / spinlock level |
| 3–26 | Dirql |
Device interrupt levels |
| 27 | Profile |
Profiling timer |
| 28 | Clock |
Clock interrupt |
| 29 | Ipi |
Inter-processor interrupt |
| 30 | Power |
Power failure |
| 31 | High |
Highest — machine check |
The golden rule
IRQL can only stay the same or be raised, never lowered.
Attempting to call a lower-IRQL function produces a compile error:
error[E0277]: IRQL violation: cannot reach `Passive` from `Dispatch` -- would require lowering
--> src/main.rs:6:5
|
= note: IRQL can only stay the same or be raised, never lowered
Examples
Basic functions
use ;
Structs and impl blocks
use ;
IRQL-safe function traits
The library provides IrqlFn, IrqlFnMut, and IrqlFnOnce — IRQL-aware
analogues of Fn, FnMut, and FnOnce. Use the same #[irql()] attribute
on trait impl blocks; the macro rewrites IrqlFn<Args> → IrqlFn<Level, Args>
automatically.
use ;
;
Range constraints with min
Use min to enforce a floor — the caller must be at or above the minimum:
use ;
// Only callable from [Passive, Dispatch] — not from Dirql or above
How it works
Type-level IRQL encoding
Each IRQL level is a zero-sized type — no runtime cost:
;
;
// … 9 levels total
Trait-based hierarchy
The IrqlCanRaiseTo trait encodes which transitions are valid:
// Passive can raise to Dispatch (impl exists)
// Dispatch cannot lower to Passive (no impl — compile error)
IrqlCanLowerTo works in the opposite direction for min constraints.
Macro expansion
#[irql(max = Dispatch)] adds an IRQL generic bounded by
IrqlCanRaiseTo<Dispatch>:
// Source:
// Expands to:
call_irql!(f()) rewrites the call to f::<IRQL>(), threading the IRQL type
through the call chain. The compiler verifies every transition.
API reference
#[irql()] attribute
| Syntax | Target | Effect |
|---|---|---|
#[irql(at = Level)] |
fn |
Fixed entry point — no generic added |
#[irql(max = Level)] |
fn, impl |
Adds IRQL: IrqlCanRaiseTo<Level> |
#[irql(min = A, max = B)] |
fn, impl |
Also adds IRQL: IrqlCanLowerTo<A> |
call_irql!
Calls an IRQL-constrained function or method, threading the IRQL type:
call_irql!; // free function
call_irql!; // method call
let d = call_irql!; // constructor
Function traits
| Trait | Analogous to | Method |
|---|---|---|
IrqlFn<Level, Args, Min = Passive> |
Fn |
call(&self, args) |
IrqlFnMut<Level, Args, Min = Passive> |
FnMut |
call_mut(&mut self, args) |
IrqlFnOnce<Level, Args, Min = Passive> |
FnOnce |
call_once(self, args) |
When writing an impl, you only provide Args — the macro fills in Level
(and Min if min is set):
// Expands to: impl IrqlFn<Passive, ()> for MyType { … }
Safety considerations
All checks are compile-time only. You must ensure:
- Entry points (
#[irql(at = …)]) match the actual runtime IRQL - IRQL-raising operations (e.g. acquiring spinlocks) are properly modelled
Best practices
- Annotate entry points with
#[irql(at = Level)]matching the real runtime IRQL - Prefer
maxfor most functions — it's the ceiling thatcall_irql!relies on - Use
minonly when a function genuinely requires a minimum IRQL floor - Keep constraints tight — don't use
max = Highwhenmax = Dispatchsuffices
License
Licensed under either of
at your option.