jax's Pallas/Mosaic lowering layer carries the highest activity risk

Analysis of jax-ml/jax at commit 0c17ff9 finds five critical-band functions in the Pallas/Mosaic lowering and core jaxpr subsystems — all in the 'fire' quadrant, all touched within the last three days, and all flagged as god functions with extreme branching complexity.

Stephen Collins ·
Generated by hotspots · free & open source
pip
$ pip install hotspots-cli
Activity Risk22.38Low
Hottest Functionjaxpr_subcomp

Antipatterns Detected

complex_branching5deeply_nested5exit_heavy5god_function5long_function5cyclic_hub1hub_function1

Run this on your own codebase

See if your own repo has a jaxpr_subcomp-style hotspot — run this in any local git repo:

pip
$ pip install hotspots-cli
npm
$ npm install -g @stephencollinstech/hotspots
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Key Points

What is a god function and why does it matter in JAX?

A god function is a function that has accumulated so many responsibilities — complex branching, numerous callees, broad coupling — that it acts as a single point of control for a large part of the system. In JAX, where functions like `lower_jaxpr_into_pipelined_module` call 67 distinct other functions, a god function becomes a hub where changes to any one of those callees may require corresponding changes here, and vice versa. This makes testing in isolation nearly impossible and means that a bug fix in one area of the lowering pipeline can introduce a regression in another area via this central function. All five of the top hotspots in JAX carry the god-function pattern, which means the Pallas/Mosaic lowering layer and core jaxpr infrastructure are concentrating risk in exactly the functions that are also being actively changed.

How do I reduce cyclomatic complexity in Python?

The most direct technique is extract-method refactoring: identify clusters of branches that handle a single logical case and move them into a named helper function. In Python specifically, replacing large if-elif chains with dispatch tables or single-dispatch decorators (`functools.singledispatch`) can collapse many branches into a lookup, which reduces cyclomatic complexity without losing the branching logic. Any function above a cyclomatic complexity of 15 warrants splitting; above 30, the refactoring should be treated as urgent rather than optional. For `_interpret_jaxpr`, with a cyclomatic complexity of 88, I'd start by extracting the handler for each jaxpr primitive into its own function — even extracting ten primitives would likely cut the effective complexity by a third and make the remaining structure legible.

Is JAX actively maintained?

Based on the data at commit 0c17ff9, every analyzed function in the repository falls into either the 'fire' or 'watch' quadrant — there are zero debt-quadrant and zero dormant functions. All five of the top hotspots were touched within the last three days (days_since_changed: 3 across all five), each recording one touch in the last 30 days. The 'fire' quadrant contains 4,272 functions. That's a codebase under continuous, broad development. Active maintenance and high structural complexity are not mutually exclusive: the Pallas/Mosaic lowering layer is clearly evolving rapidly, and the structural complexity in those functions is accumulating alongside that activity.

How do I reproduce this analysis?

The Hotspots CLI is available at github.com/hotspots-dev/hotspots. After installing, check out the exact commit analyzed here with `git checkout 0c17ff9`, then run `hotspots analyze . --mode snapshot --explain-patterns --force` from the repository root. The same command works on any local git repository without additional configuration — no setup files required.

What does activity-weighted risk mean?

Activity-weighted risk multiplies structural complexity — derived from cyclomatic complexity, nesting depth, and fan-out — by recent commit frequency, so functions that are both hard to understand and actively changing score the highest. A function with a cyclomatic complexity of 80 that hasn't been modified in two years scores considerably lower than one with cyclomatic complexity of 20 that is touched every week, because the dormant complex function poses lower near-term regression risk. The goal of this prioritization is to surface functions where a bug is most likely to be introduced right now — not just where the code looks complicated in the abstract — so that refactoring effort lands where it actually reduces the probability of a defect shipping. The top scorer here, `jaxpr_subcomp`, reaches a risk score of 22.38 by combining a cyclomatic complexity of 56 and fan-out of 35 with a change just three days ago.

At commit 0c17ff9, jax-ml/jax has 12,424 analyzed functions, of which 1,404 are in the critical band — and every one of the top five hotspots is ‘fire’ quadrant, meaning structurally complex and actively changing right now. The highest scorer, jaxpr_subcomp in jax/_src/pallas/mosaic/lowering.py, carries a risk score of 22.38, with a cyclomatic complexity of 56 and a fan-out of 35 — and it was modified three days ago. That combination is the definition of live regression risk: an engineer touching this function today is navigating 56 independent execution paths and 35 distinct callees simultaneously. I would start the review here, then work down through _interpret_jaxpr and lower_jaxpr_into_pipelined_module, which share the same file or subsystem and compound the blast radius.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top 5 Hotspots

FunctionFileRiskCCNDFO
jaxpr_subcompjax/_src/pallas/mosaic/lowering.py22.456735
_interpret_jaxprjax/_src/pallas/mosaic/interpret/interpret_pallas_call.py19.788644
_parse_hlo_new_formatjax/experimental/source_mapper/hlo.py19.248718
_check_jaxprjax/_src/core.py19.145530
lower_jaxpr_into_pipelined_modulejax/_src/pallas/mosaic/lowering.py19.072567

Large Repo Analysis

jax is a large repository. To stay within memory constraints, this analysis used hybrid touch mode: structural complexity — CC, ND, FO — is measured precisely for every function. Git activity is tracked at the function level (via git log -L) only for files with 5 or more commits in the last 30 days; other files use a file-level approximation. Rankings therefore surface functions that are both structurally complex and in the most actively-changing parts of the codebase. Dormant code with high structural complexity will rank lower than it would under a full per-function analysis — to surface it, run hotspots analyze . --per-function-touches on a machine with sufficient memory.

JAX is Google’s numerical computation library built around composable function transformations — jit, grad, vmap — and its Pallas extension brings kernel programming to TPU (Mosaic) and GPU (Mosaic GPU) backends. The functions that translate JAX’s internal representation (jaxpr) into hardware-specific code are, structurally speaking, the most demanding in the codebase. That’s exactly where the risk concentrates.

Triage Band Distribution
Fire4272Watch8152

12,424 functions analyzed

Every function in this repository falls into either the ‘fire’ or ‘watch’ quadrant — zero debt, zero dormant functions. That means there’s no low-activity shelter here: even the complex functions are being touched. The 4,272 ‘fire’-quadrant functions are the ones that demand immediate attention.

Detected Antipatterns
Complex Branching×5Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.
Deeply Nested×5Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.
Exit Heavy×5Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.
God Function×5God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.
Long Function×5Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
Cyclic Hub×1Cyclic Hub
Participates in a call cycle with other high-traffic functions, creating circular dependency risk.
Hub Function×1Hub Function
Many other functions call this one — a change here ripples widely through callers.

Every single top-five function is flagged simultaneously for complex branching, deep nesting, multiple exit paths, god-function coupling, and excessive length. That’s not a coincidence — it reflects the inherent difficulty of lowering a general-purpose IR to multiple specialized hardware backends. But difficulty of domain doesn’t reduce the maintenance burden; it amplifies it.


jaxpr_subcompjax/_src/pallas/mosaic/lowering.py

jaxpr_subcomp
jax/_src/pallas/mosaic/lowering.py
22.38
critical
CC 56
ND 7
FO 35
touches/30d 1

By name and path, jaxpr_subcomp handles sub-expression compilation of jaxprs in the Mosaic (TPU) lowering pipeline. The metrics tell a pointed story: cyclomatic complexity of 56 means there are 56 independent execution paths through this function — each one a distinct test case that may or may not exist. A nesting depth of 7 means there are control structures seven levels deep, which makes it genuinely hard to reason about which branch you’re on at any given point without an IDE and significant patience. The fan-out of 35 distinct callees is particularly sharp in Python, where duck typing means those 35 call sites may resolve to different concrete types at runtime — the coupling is broader than the number alone suggests.

Cyclomatic Complexity 56
threshold: 10
Fan-Out 35
threshold: 15

This function also carries both the cyclic_hub and hub_function patterns — the only function in the top five to do so. That means it’s not just complex internally; it occupies a central position in the call graph where changes here can create unexpected ripple effects across callers and callees alike. The file has two commits and two authors in the last 90 days, and jaxpr_subcomp was touched three days ago. There’s no historical bug-linked commit signal, which is worth noting: this isn’t a function with a documented defect history, but its structural profile means any change made under time pressure is navigating significant complexity. My recommendation here is to identify the distinct lowering cases embedded in those 56 paths and extract each into a named helper — even pulling out three or four would reduce the effective CC and make the branching structure legible.


_interpret_jaxprjax/_src/pallas/mosaic/interpret/interpret_pallas_call.py

_interpret_jaxpr
jax/_src/pallas/mosaic/interpret/interpret_pallas_call.py
19.68
critical
CC 88
ND 6
FO 44
touches/30d 1

_interpret_jaxpr lives in the Pallas Mosaic interpreter — the path that runs a Pallas kernel through a software simulation rather than compiled hardware execution. Interpretation of a general jaxpr requires dispatching on every primitive operation, handling control flow, managing environments, and dealing with all the edge cases that compiled lowering can sometimes elide. A cyclomatic complexity of 88 is the highest in the top five and places this firmly in the “extreme” range — 88 paths through a single function. The nesting depth of 6 and fan-out of 44 compound that: 44 distinct callees in a dynamically typed language means the actual coupling surface is wide and partially invisible until runtime.

Cyclomatic Complexity 88
threshold: 10
Fan-Out 44
threshold: 15

The exit_heavy pattern flags multiple return paths, each of which is an implicit contract about what state the caller receives. Combined with 88 branch points, achieving meaningful test coverage here is a significant undertaking. The file has a single commit and a single author in the last 90 days, and no bug-linked commits on record. That single-author concentration is worth tracking: if this interpreter path is modified again, the structural complexity demands that more than one engineer reviews the change. My recommendation is to apply extract-method refactoring primitive-by-primitive — each primitive handler is a natural extraction candidate that would reduce CC by several points per extraction and make the interpreter’s dispatch logic explicit rather than embedded in one monolithic function.


_parse_hlo_new_formatjax/experimental/source_mapper/hlo.py

_parse_hlo_new_format
jax/experimental/source_mapper/hlo.py
19.19
critical
CC 48
ND 7
FO 18
touches/30d 1

The experimental/ prefix in the file path and the function’s _new_format suffix are both worth flagging. This is explicitly experimental infrastructure — a source mapper for HLO (High Level Operations, XLA’s IR) — and the _new_format name implies it’s handling a format transition, possibly alongside an older parsing path. A cyclomatic complexity of 48 and nesting depth of 7 in a parser that is not yet stable means the team is actively iterating on something that hasn’t settled. HLO format parsing is inherently branchy — it needs to handle every operation type, every attribute variant, and format-version differences — but CC 48 with ND 7 suggests the format handling has grown organically without structural decomposition.

Cyclomatic Complexity 48
threshold: 10
Max Nesting Depth 7
threshold: 4

The god_function and long_function patterns together suggest this is absorbing responsibilities that belong in smaller, focused parsers. No bug-linked commits exist at the file level, and there’s a single author in the last 90 days. Given that this lives under experimental/, I’d prioritize adding a parsing-stage test suite before the next format iteration, then decompose the function by HLO node category. A parser of this complexity warrants explicit grammar-level decomposition — each top-level HLO construct as its own parsing function — rather than a single monolith.


_check_jaxprjax/_src/core.py

_check_jaxpr
jax/_src/core.py
19.13
critical
CC 45
ND 5
FO 30
touches/30d 1

_check_jaxpr in jax/_src/core.py is the jaxpr validity checker — the function that verifies structural correctness of JAX’s internal program representation before it gets compiled or transformed. This is foundational infrastructure: nearly every JAX transformation path runs through jaxpr validation at some point. A cyclomatic complexity of 45 is consistent with a function that must check every possible jaxpr construct, variable binding rule, and type constraint. The fan-out of 30 reflects the breadth of what it validates — each callee is likely a type predicate, an environment lookup, or a sub-check for a specific jaxpr primitive.

Cyclomatic Complexity 45
threshold: 10
Fan-Out 30
threshold: 15

What makes this function higher priority than its raw complexity score might suggest is its position in core.py. This is not an experimental path or a backend-specific lowering — it’s the central validity gate for the entire system. The exit_heavy pattern means there are multiple early-exit paths, which in a checker context likely correspond to different failure modes, each implicitly encoding a type of jaxpr invariant violation. The file has two commits and a single author in the last 90 days, with no bug-linked history. My recommendation here is to decompose the checker by jaxpr node kind — each primitive, each sub-expression type, each binding rule — into individually testable check functions. That both reduces CC and makes invariants explicit and auditable.


lower_jaxpr_into_pipelined_modulejax/_src/pallas/mosaic/lowering.py

lower_jaxpr_into_pipelined_module
jax/_src/pallas/mosaic/lowering.py
19.05
critical
CC 72
ND 5
FO 67
touches/30d 1

This is the second function from jax/_src/pallas/mosaic/lowering.py in the top five, and it’s the one with the most striking single metric in the entire dataset: a fan-out of 67. Sixty-seven distinct functions called from one function. In Python, where argument types are resolved at runtime and decorator stacks can redirect dispatch invisibly, a fan-out of 67 is not 67 dependencies — it’s a lower bound on the actual coupling surface.

Fan-Out 67
threshold: 15
Cyclomatic Complexity 72
threshold: 10

By name, this function orchestrates lowering a jaxpr into a pipelined Mosaic module — the TPU’s execution model for overlapping computation and memory transfers. Pipelining inherently involves staging, buffering, and sequencing decisions, which explains some of the complexity, but CC 72 with ND 5 and FO 67 goes well beyond what the domain strictly requires. The god_function pattern here is apt: this function appears to be accumulating pipeline configuration, buffer allocation, and lowering orchestration responsibilities that could be separated. Two authors have touched this file in the last 90 days, and the function was last changed three days ago. Given it shares a file with jaxpr_subcomp, any refactoring in this file should be coordinated — decomposing both functions in isolation risks creating new coupling between the extracted helpers. I’d treat this file as a single refactoring unit and plan the work accordingly.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
complex_branching5
deeply_nested5
exit_heavy5
god_function5
long_function5
cyclic_hub1
hub_function1

These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.

See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function.

Reproduce This Analysis

git clone https://github.com/jax-ml/jax
cd jax
git checkout 0c17ff9c866088155e969799766e54925c1ad0dd
hotspots analyze . --mode snapshot --explain-patterns --force --hybrid-touches 5

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →

Was this useful? Let me know →

Related Analyses