JIT Compilation
The jit feature flag enables Just-In-Time compilation of HBAC rules via
Cranelift. It adds a hybrid JIT/interpreter runtime as an additional
optimization layer in the evaluation pipeline.
Enabling
[dependencies]
hbac-rs = { version = "0.2", features = ["jit"] }
#![allow(unused)]
fn main() {
let mut policy = HbacPolicy::new();
policy.load_rules(rules);
policy.enable_jit();
for request in requests {
policy.evaluate(&request);
}
}
enable_jit() returns false if Cranelift is not available for the
current architecture (only x86_64 is supported).
How It Works
The JIT runtime is built around a HotRuleProfiler that tracks per-rule
evaluation counts and flags a rule as hot once it has been recorded at least
100 times. policy.evaluate() feeds the profiler automatically: every time
the decision-tree interpreter inspects a SingleUser or GroupMatch rule
(the only patterns it ever walks – see below), that rule’s count is
incremented, and a Deny-typed rule that crosses the threshold is
compiled immediately, with no separate call required. Rules that are not
hot, not Deny-typed, or cannot be compiled fall back to the standard
decision tree interpreter.
CategoryAll rules – the only pattern with real native Cranelift codegen
– are never profiled or auto-compiled: RulePipeline’s constant-result
fast path (see
Caching Architecture, Layer 0)
resolves a bare category=all rule set before JitRuntime is ever
consulted, so there’s nothing to profile and no request that a compiled
version could speed up further.
Allow-typed rules are intentionally never auto-compiled, even once hot: the
runtime’s jit_cache loop below only short-circuits on a Deny match, so a
compiled Allow entry would still run on every future request with its
result discarded, and the interpreter would re-evaluate everything anyway –
pure overhead for no benefit.
flowchart TD
EVAL[evaluate request] --> JIT{JIT-compiled<br/>deny rules?}
JIT -->|match| DENY[return deny]
JIT -->|no match| INTERP[decision tree<br/>interpreter]
INTERP --> RESULT[return result]
INTERP --> PROFILE[profiler records<br/>each SingleUser/GroupMatch<br/>rule checked]
PROFILE --> HOT{count >= 100<br/>and rule_type == Deny?}
HOT -->|yes| COMPILE[compile via Cranelift]
COMPILE --> CACHE[store in jit_cache]
The runtime checks JIT-compiled deny rules first for early exit, then
delegates to the interpreter for the full evaluation, profiling and
auto-compiling as it goes. jit_compile_hot() remains available to force
eager compilation of any already-hot rule without waiting for the next
evaluate() call to trigger it – for example, right after a bulk rule
reload and before traffic starts flowing.
Rule Classification
The compiler classifies each rule into a pattern that determines the compilation strategy:
| Pattern | Criteria | Compilation |
|---|---|---|
CategoryAll | All three dimensions are category=all | Native Cranelift codegen (constant return) |
SingleUser | One user name, any host/service | Optimized interpreter (direct string comparison) |
GroupMatch | User groups specified, any host/service | Optimized interpreter (set membership check) |
Complex | Multiple users, or other patterns | Standard interpreter (not compiled) |
Only CategoryAll rules produce actual native machine code. SingleUser
and GroupMatch use pattern-specific data cached at classification time for
faster interpreter evaluation, but do not generate native code. Of these,
only Deny-typed rules are ever automatically compiled – see
How It Works for why Allow-typed and CategoryAll rules
are excluded.
Internals
Components
JitCompiler– Cranelift-based compiler. Owns theJITModule, declares and finalizes functions.HotRuleProfiler– LRU-based evaluation counter (256 entries). Tracks per-rule evaluation counts and identifies rules above the JIT threshold.JitRuntime– Orchestrates profiling, compilation, and evaluation. Maintains ajit_cachemapping rule names to compiled evaluators.CompiledRule– Implements theRuleEvaluatortrait. Holds either a native function pointer (CategoryAll) or pattern-specific data for the optimized interpreter fallback.
RuleEvaluator Trait
#![allow(unused)]
fn main() {
pub trait RuleEvaluator: Send + Sync {
fn evaluate(&self, request: &HbacRequest) -> bool;
fn debug_info(&self) -> String;
}
}
All compiled rules implement this trait. CompiledRule dispatches to the
native function pointer when available, otherwise uses the cached pattern
data.
Safety
CompiledRule contains a raw function pointer to JIT-compiled code. The
pointer is safe because:
- It is obtained from
JITModule::get_finalized_function()after finalization - The backing
JITModuleis owned byJitCompiler, which is owned by the sameJitRuntimethat owns theCompiledRulein itsjit_cache - The compiled code is read-only after finalization (
Send + Sync)
Statistics
#![allow(unused)]
fn main() {
let stats = policy.jit_stats();
println!("total: {}", stats.total_rules);
println!("compiled: {}", stats.jit_compiled);
println!("hot: {}", stats.hot_rules);
}
jit_compile_hot() manually triggers compilation of all Deny-typed rules
that the HotRuleProfiler has already recorded as hot – useful to force
compilation eagerly (e.g. right after a bulk reload) rather than waiting for
the 100th matching request to trigger it automatically:
#![allow(unused)]
fn main() {
policy.jit_compile_hot();
}
Performance Impact
See Performance Results for measured JIT
impact at each scale. That page predates automatic hot-rule profiling and
should be treated as stale until re-measured with crates/perf-testing’s
--jit benchmark scenario. Structurally, the benefit is still expected to
be small for most deployments: the LRU cache and decision tree already
provide sub-microsecond cached evaluation, and only Deny-typed
SingleUser/GroupMatch rules evaluated 100+ times against a sufficiently
diverse request stream (i.e. not absorbed by the LRU cache) are eligible for
compilation at all.
For typical SSSD deployments with high cache hit rates, jit is unlikely to
provide a measurable benefit.
Binary Size
The jit feature adds ~500 KB from Cranelift dependencies.
See Also
- Architecture Overview – JIT layer in the evaluation flow
- Performance Results – benchmark data