IRQL violations cause blue screens. This crate catches them at compile time.
Zero runtime cost. Zero binary overhead.
[dependencies]
irql = "0.1.6"use irql::{irql, Dispatch, Passive};
#[irql(max = Dispatch)]
fn acquire_spinlock() { /* … */ }
#[irql(max = Passive)]
fn driver_routine() {
call_irql!(acquire_spinlock()); // Passive can raise to Dispatch
}
#[irql(at = Passive)]
fn driver_entry() {
call_irql!(driver_routine());
}If it compiles, your IRQL transitions are valid.
#[irql(max = Dispatch)] adds a hidden IRQL type parameter bounded by IrqlCanRaiseTo<Dispatch>. call_irql! threads it through every call as a turbofish argument. The compiler checks every transition — trying to lower IRQL is a compile error:
error[E0277]: IRQL violation: cannot reach `Passive` from `Dispatch`
-- would require lowering
| Form | Meaning |
|---|---|
#[irql(at = Level)] |
Fixed entry point — known IRQL, no generic |
#[irql(max = Level)] |
Callable from Level or below |
#[irql(min = A, max = B)] |
Callable in [A, B] |
Works on functions, impl blocks, and trait impl blocks.
| Value | Type | Description |
|---|---|---|
| 0 | Passive |
Normal thread; paged memory OK |
| 1 | Apc |
APC delivery |
| 2 | Dispatch |
DPC / spinlock |
| 3–26 | Dirql |
Device interrupts |
| 27 | Profile |
Profiling timer |
| 28 | Clock |
Clock interrupt |
| 29 | Ipi |
Inter-processor interrupt |
| 30 | Power |
Power failure |
| 31 | High |
Highest — machine check |
Apply #[irql] to an entire impl block — every method gets the constraint:
struct Device { name: &'static str }
#[irql(max = Dispatch)]
impl Device {
fn new(name: &'static str) -> Self { Device { name } }
fn process(&self) { /* … */ }
}IrqlFn, IrqlFnMut, IrqlFnOnce — IRQL-aware analogues of Fn, FnMut, FnOnce:
#[irql(max = Passive)]
impl IrqlFn<()> for Reader {
type Output = u32;
fn call(&self, _: ()) -> u32 { self.value }
}The macro rewrites IrqlFn<()> to IrqlFn<Passive, ()> automatically.
irql = { version = "0.1.6", features = ["alloc"] }Requires nightly (allocator_api, vec_push_within_capacity, auto_traits, negative_impls).
| Pool | Allocable at | Accessible at |
|---|---|---|
PagedPool |
Passive, Apc |
Passive, Apc |
NonPagedPool |
Passive, Apc, Dispatch |
Any IRQL |
IrqlBox::new and IrqlVec::new pick the cheapest legal pool automatically.
#[irql(max = Passive)]
fn example() -> Result<(), AllocError> {
let data = call_irql!(IrqlBox::new(42))?;
let val = call_irql!(data.get());
let v = irql_vec![1, 2, 3]?;
call_irql!(v.push(42))?;
// FFI: transfer ownership via raw pointer
let ptr = data.into_raw();
let data = unsafe { IrqlBox::<_, PagedPool>::from_raw(ptr) };
Ok(())
}Paged-pool containers cannot be dropped at Dispatch or above. The #[irql] macro injects SafeToDropAt<Level> bounds on by-value parameters, so passing paged-pool memory into elevated-IRQL code is a compile error. References (&IrqlBox) are not gated. Use leak() or into_raw() to transfer ownership across IRQL boundaries.
irql ← public facade (re-exports everything)
├── irql_core ← levels, hierarchy traits, function traits, SafeToDropAt
├── irql_macro ← #[irql] proc macro, call_irql! rewriter
└── irql_alloc ← IrqlBox, IrqlVec, pool allocator (optional, nightly)
All checks are compile-time only. You must ensure entry points (#[irql(at = …)]) match the actual runtime IRQL and that IRQL-raising operations are properly modelled.
MIT or Apache 2.0, at your option.