Skip to content

Safety rails & budgeting

Obfuscation multiplies IR. Left unchecked, a heavy annotation on a large function can blow up compile time and binary size. xollvm gates every transform on size thresholds and an IR-growth budget, so obfuscation degrades gracefully instead of exploding.

Function-size gates

Skip functions that are too big or too deeply nested before any pass touches them:

Flag Default Meaning
-obf-max-function-insts=<N> 0 (off) Skip functions with more than N instructions.
-obf-max-function-blocks=<N> 0 (off) Skip functions with more than N basic blocks.
-obf-max-loop-depth=<N> 0 (off) Skip functions whose loop nesting exceeds N.

0 disables a gate. These are global — they apply to every annotated function.

The IR-growth budget

Each function has a budget: a ceiling on how much its instruction count may grow across all passes. When a pass would exceed it, sites are throttled or skipped rather than emitted.

Flag Default Meaning
-obf-ir-budget-multiplier=<N> 50 Budget = insts_before × N (clamped by the max below). 0 = unlimited.
-obf-ir-budget-max=<N> 0 (off) Absolute per-function instruction ceiling. 0 = no hard cap.

So by default a function may grow up to 50× its original size, with no absolute cap. Tighten both for size-sensitive targets:

opt -passes=obfuscation app.ll -S -o app.obf.ll \
    -obf-ir-budget-multiplier=20 -obf-ir-budget-max=8000

Two enforcement tiers:

  • The multiplier limit is advisory — budget-aware passes self-throttle toward it, and the driver skips remaining passes once it is already exceeded before a pass runs.
  • The hard cap (-obf-ir-budget-max) is a hard guarantee. If a single pass expands a function past the cap, the driver rolls that function back to its pre-pass body and records the pass as skipped (reason budget_rollback). This stops a non-throttling or pathological pass (e.g. flattening on a nasty CFG) from ever shipping a function larger than the cap. With no hard cap set (the default) no snapshot is taken and output is unchanged.

Budgets: global or per-function

Budgets can be set globally via the command-line flags above, or per function via annotation tokens — budget= / budgetMultiplier= (the multiplier) and budgetMax= (the hard cap). A per-function value overrides the global default for that function (last annotation wins). Per-pass caps like maxSites / maxBlocks are per-annotation too and stack on top of the budget.

Reading budget pressure

When the report shows many "skipped due to budget" entries, the function hit its ceiling. Two ways to respond:

  • Want more obfuscation: raise -obf-ir-budget-multiplier / -obf-ir-budget-max.
  • Want smaller output: lower per-pass prob, maxDepth, loop, maxSites, or annotate fewer functions.
# see what was skipped and why
opt -passes=obfuscation app.ll -S -o app.obf.ll -obf-report-dir=obf_report -obf-verbose

The report JSON records budget utilisation per function; -obf-verbose prints skip reasons live. See Reports & CFG visualization.

Correctness rails

Beyond size, two flags guard correctness and debuggability:

Flag Default Meaning
-obf-verify off Run IR verification before/after each stage — catches an invalid transform close to its source.
-obf-debug-synthetic on Give inserted instructions synthetic line-0 debug locations so source steppers don't jump erratically.

Keep -obf-verify on while developing an annotation set; it turns a mysterious downstream crash into a precise "this pass produced invalid IR" error.