Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

PatternCriteriaCompilation
CategoryAllAll three dimensions are category=allNative Cranelift codegen (constant return)
SingleUserOne user name, any host/serviceOptimized interpreter (direct string comparison)
GroupMatchUser groups specified, any host/serviceOptimized interpreter (set membership check)
ComplexMultiple users, or other patternsStandard 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 the JITModule, 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 a jit_cache mapping rule names to compiled evaluators.
  • CompiledRule – Implements the RuleEvaluator trait. 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 JITModule is owned by JitCompiler, which is owned by the same JitRuntime that owns the CompiledRule in its jit_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