A Python library that gives developers hint-based control over CPython's JIT compiler via decorators. The public API is stable today; the backend strategies evolve as CPython exposes more native JIT control.
CPython 3.13 introduced an experimental JIT (copy-and-patch). Python 3.14 continues that work. But as of now, no public per-function API exists to tell the JIT what to prioritize, skip, or cache. Developers have no knobs.
jitctl bridges that gap with a stable decorator API that works today through inference and warm-up tricks, and will transparently upgrade to native hooks when CPython exposes them.
@jit_focus # I think this function is hot — do your best to JIT it
@jit_ignore # Don't bother JIT-ing this — it's cold, infrequent, or has side effects
@jit_once # JIT it, but don't re-specialize — I call it with many different typesThese are the only things users import. Everything else is internal.
- Should the decorators be no-ops that return the original function on non-JIT builds, or should they always apply the warmup strategy? Leaning toward: always apply strategy (warmup is useful even without JIT), but gate any introspection/profiling overhead behind a JIT-detected build.
This is the heart of the library. It does two things:
- Detects the Python version and JIT availability at import time using
sys.version_infoand checking for JIT-specific attributes (e.g.sys._jit_enabled()if/when that lands, or inspectingsys.flags). - Returns the right strategy object for a given decorator. Today that's always the warmup strategy. In future Python versions it could be a native hook.
_backend.get_strategy("focus") → WarmupStrategy | NativeJITStrategy
The rest of the codebase never checks the Python version directly — it always asks _backend.
- CPython issue tracker: bpo/gh issues around
sys.set_jit_level()or per-function JIT control sys._jit_enabled()— rumored, not confirmed in 3.14 yetcode.co_qualnameandcode.co_flagschanges that might signal JIT tier
Warm-up works by calling the function N times with synthetic/dummy arguments before returning it to the caller. This causes CPython's specializing adaptive interpreter to tier up the bytecode faster.
- How many warmup iterations? CPython's threshold to tier-1 specialization is around 8–16 calls; tier-2 (actual JIT) is higher (~100). Need to benchmark. Start with a configurable default (e.g. 50).
- What dummy args to use? This is the hard part. Options:
- Inspect the function signature and generate typed defaults (
int→0,str→"", etc.) usinginspect.signature()+_introspect.py. - Require the user to pass
sample_argsto the decorator:@jit_focus(sample_args=(1, 2)). - Call with no args and catch
TypeErrorsilently. Simple but unreliable. - Recommendation: start with
sample_argsas an optional parameter; fall back to no-arg call.
- Inspect the function signature and generate typed defaults (
- When does warmup run? Options: at decoration time (module load), at first call, or lazily after N real calls. At decoration time is simplest but can slow imports.
Wraps the function to track:
- How many times it's been called
- Average/min/max execution time
Used by @jit_focus to verify the warmup actually had an effect, and potentially to feed adaptive decisions (e.g. re-warm if performance degrades, which might indicate de-optimization).
Also useful for a future jitctl.stats() public API that lets developers inspect what the library actually did.
CPython exposes a function.__code__ object (a code object) with attributes like:
co_argcount— number of positional argsco_varnames— local variable namesco_flags— bitmask of compiler flagsco_consts,co_names— constants and global names referenced
Use this to:
- Infer argument types for warmup dummy args
- Detect functions that are likely JIT-unfriendly (e.g. those that use
eval,exec, or dynamic__code__replacement) - Check if a function is already "warm" before doing unnecessary warmup
This is the most exploratory module — read CPython docs and source on code objects before implementing.
Single place to normalize anything that changed between 3.13 and 3.14+. Examples:
- If a new
sysattribute appears in 3.14, access it here with a fallback for 3.13. - Any bytecode-level differences relevant to JIT tier detection.
Keep this minimal — only add a shim when a real divergence is found.
_compat.py— trivial, justsys.version_infochecks_backend.py— version detection + strategy interface (no real strategies yet, just stubs)_introspect.py— readco_*attributes, no side effects, easy to test in isolation_profiler.py— simple wrapper, no JIT knowledge needed_warmup.py— depends on_introspect.pyfor dummy arg inference__init__.py— wire up decorators through_backend.py
- On a non-JIT CPython build (e.g. regular 3.13 without
--enable-experimental-jit): decorators should be transparent no-ops — return the original function unchanged, zero overhead. - On PyPy or other non-CPython: same — no-ops.
- On a JIT-enabled build with no native hook API: apply warmup strategy.
- On a future build with native JIT hooks: use them via
_backend.py.
This means _backend.py must detect both "is this CPython?" and "is JIT enabled?" before doing anything.
- Should
@jit_onceactually suppress re-specialization, or just warm up once and add a profiler guard that skips re-warmup? There is no CPython API today to prevent re-specialization. - Should the library expose a
jitctl.stats()function that prints what each decorated function did? Useful for debugging, but adds surface area. - Should warmup happen at decoration time or first call? This affects import performance.
- What's the
sample_argsUX?@jit_focus(sample_args=(1, 2))vs@jit_focuswith auto-inference?