XLA Up Close: What It Optimizes, and What It Won't XLA, the compiler behind JAX, TensorFlow, and PyTorch/XLA, optimizes array programs by freezing shapes, statically allocating buffers, and fusing operations against a global cost model, which makes it fast but rigid. A demonstration shows XLA eliminating add-zero, multiply-one, and double-transpose operations from a simple function, proving its algebraic-simplification strength. The same design decision that enables high performance also causes inflexibility, as XLA's strengths and limitations stem from its ahead-of-time compilation and static analysis approach. XLA Up Close: What It Optimizes, and What It Won't Here is a small function and the entire program XLA compiled it into: 1 2 3 python def simp x : y = x + 0.0 1.0 return jnp.transpose jnp.transpose y 1 2 3 4 php ENTRY %main x: f32 3,3 - f32 3,3 { %x = f32 3,3 {1,0} parameter 0 ROOT %copy = f32 3,3 {1,0} copy %x } The add-zero, the multiply-one, and both transposes are gone. XLA proved the whole thing is the identity and emitted a single copy. This is not a toy result; it is the same algebraic-simplification machinery that lets XLA take a page of dense linear algebra and hand back a fused kernel that runs at the memory-bandwidth limit of a TPU. XLA is a genuine optimizing compiler, and a very good one. 1 fn:1 It is also rigid in ways that will bite you, and the interesting thing — the thing worth a long post — is that its strengths and its limitations are the same design decision . XLA freezes your shapes, sees the whole graph at once, allocates every buffer statically, and fuses against a global cost model you do not control. That is exactly why it is fast, and exactly why it is inflexible. This is a review and a critique of that bargain. I will show you what XLA actually does, using real HLO dumps, sourcing each claim to the OpenXLA source and docs, and then I will tell you where the bargain stops paying off. If you have not met the layer above XLA: it is the compiler behind JAX, behind TensorFlow, and behind PyTorch through PyTorch/XLA. 1 JAX traces your Python into a small typed IR and hands it to XLA; what XLA does with it from there is the subject here. The tracing half — how Python becomes that IR — is a topic of its own, and I leave it aside. Where XLA sits, and what it eats XLA is an ahead-of-time compiler for array programs. “Ahead of time” is relative to execution , not to your process: it compiles the first time a given shape signature appears, caches the result, and reuses it. 2 Its input is a graph of high-level tensor operations, and the modern portable front door to that graph is StableHLO , an MLIR dialect that JAX emits and that XLA ingests. The full path: 3 fn:3 1 2 3 4 jaxpr ──▶ StableHLO ──▶ HLO ──▶ optimization passes ──▶ optimized HLO ──▶ backend ├─ LLVM CPU ├─ LLVM/PTX + cuBLAS/cuDNN/Triton GPU └─ XLA:TPU closed backend The naming here is a genuine mess and worth untangling once. There is the classic HLO — a hand-written C++ IR with its own protobuf and text format that predates MLIR and is explicitly not based on it. 4 There is the MLIR world layered on later: MHLO , and then StableHLO , which is built on MHLO and adds serialization plus backward/forward compatibility guarantees, so that it can serve as a stable portability contract between frameworks and compilers. JAX lowers to StableHLO; XLA converts StableHLO into its internal HLO and optimizes that. 3 fn:3 5 fn:2 Even the acronym is unsettled — XLA’s own architecture page glosses HLO as “high level operations” while its terminology page says “High Level Optimizer.” 2 fn:7 5 fn:2 I will use “HLO” for the thing XLA optimizes and “StableHLO” for what it is handed, because that is how the dumps read. 4 fn:4 Here is what a jit -ed relu x @ W + b looks like when JAX hands it over, straight from jax.jit f .lower ... .as text : 1 2 3 4 5 6 7 8 9 10 11 12 func.func public @main %arg0: tensor<4x8xf32 , %arg1: tensor<8x16xf32 , %arg2: tensor<16xf32 - tensor<4x16xf32 { %0 = stablehlo.dot general %arg0, %arg1, contracting dims = 1 x 0 : tensor<4x8xf32 , tensor<8x16xf32 - tensor<4x16xf32 %1 = stablehlo.broadcast in dim %arg2, dims = 1 : tensor<16xf32 - tensor<1x16xf32 %2 = stablehlo.broadcast in dim %1, dims = 0, 1 : tensor<1x16xf32 - tensor<4x16xf32 %3 = stablehlo.add %0, %2 : tensor<4x16xf32 %cst = stablehlo.constant dense<0.000000e+00 : tensor