Halide 22.0.0
Highlights
- New scheduling directives:
eager_inline(),change_type(),hoist_invariants(),split_storage,stream_loads/stream_stores,aligned_split, a GPU max-register cap, andPipeline::sever(). - New profiler with counters, recompute statistics and automatic performance warnings.
- Halidoscope, a new interactive trace and profiling visualizer.
- Struct types are now supported in the language.
MultiRamp, a better representation of affine vectors (nested ramps), with simplifier and codegen work built on it.- ARM: SVE2 and SME2 streaming-SVE support, plus new Neon patterns.
- Docs and tutorials moved to Sphinx, and all C++ tutorials were ported to Python.
- Contributor tooling: pre-commit checks, a fuzz-test framework, and Claude skills for scheduling and simplifier rules.
Breaking changes and deprecations
Build and platform
- Dropped Python 3.9 (#8830). Python 3.12 is now required, and the bindings target the stable ABI via nanobind (#9618).
- Dropped LLVM 20 (#8998). LLVM 21, 22 and 23 are supported.
- Removed the
Halide_BUNDLE_STATICfeature (#8801). - Removed
FetchContentfrom the compiler, hannk and onnx (#8962, #9001, #9005). Dependencies now come from vcpkg or the system. - The autoschedulers now have their own CMake package (#9292).
- Fixed the
Halide/HalideHelpersCMake package dependency inversion (#9234). - Python packages are split, and there is a standalone AOT runtime (#9272).
- CUDA: per-capability libdevice bitcode is replaced by a unified
libdevice.10.bc(#9226).
C++ API and ABI
- ABI:
halide_type_tno longer has alanesfield. It is now{code, bits, reserved}, andhalide_buffer_tkeeps its size and layout.Halide::Typeis a separate language-level type that must be explicitly converted withas_abi(#9205). Forloops now store an inclusivemin/maxinstead ofmin/extent(#8858).- Removed the confusing
lanesparameter fromHandle. Use.with_lanes()for vectors of handles (#9198). - The
IRMutator/IRVisitorentry point is nowoperator(). Custom lowering passes that call custom mutators or visitors may need updating (#9023). Func::in()andFunc::clone_in()are now transitive (#9117) and eager (#9277).- Removed the
CompilerLogger(#9269). - Removed the ability to iterate over a
Scope(#9594). - Renamed
CodeGen_PosixtoCodeGen_CPU(#9097). - Deprecated
Target::OS::OSXin favor ofMacOS(#9451). The"osx"target string is still accepted, with a warning. - Deprecated
TailStrategy::Predicatein favor ofGuardWithIf(#9446).
Python
- The axis convention between
hl.Bufferand the Python buffer protocol is now consistent across the bindings,PyCallableand the Python extension (#8953, #8931).
Tests
- Removed
test/error/. Its tests are now correctness tests (#9463). - Moved the internal self-tests out of libHalide into
test/correctness(#9276).
New features
Scheduling
eager_inline()(#9256):g.eager_inline({f1, ..., fN})immediately and destructively inlines the bodies of the listed Funcs intog, in order, so direct calls between them are inlined too.change_type()(#9257): replaces a Func's computation type with another (e.g. float → int32) by computing in the new type and casting back. It uses value-bounds analysis to prove no values change, adds runtime asserts when a reduction might be long enough to overflow, and rewrites products into widening intrinsics where possible. Unsafe float product accumulation can also be fused into an fma (#9578).hoist_invariants()(#9190): likerfactor, but pulls factors that are invariant over a reduction out of the accumulation when the operators obey a distributive (semiring) law. Multi-term reductions are split into one intermediate per term, returned as aFuncVec(#9419). This lets quantized kernels be written naively and scheduled into efficient ones.split_storage(#9460): splits a storage dimension into inner and outer dimensions to express swizzled memory layouts. This helps feed dot-product instructions and matrix-multiply accelerators, and avoids shared-memory bank conflicts. It works on Halide-allocated intermediates only and doesn't compose withfold_storage.stream_loadsandstream_stores(#9207): request non-temporal accesses for a stage's loads (all of them, or from named Funcs) and stores, with a fence after streamed stores. On an Apple M3 this fully recoversmemcpyperformance.- Aligned splits (#9409, #9501):
splittakes an optional offset so the first inner-loop iteration can be aligned to a boundary other than the Func's min. Inner loops now always run from 0 to the factor, which helps constant-bounds analysis. - GPU register cap (#9389): a scheduling directive replaces the
HL_CUDA_MAX_REGISTERSenvironment variable for limiting registers per thread. It only affects CUDA. Pipeline::sever()(#9484): replaces a set of Funcs withImageParaminputs and returns a second pipeline that computes them. Work that would otherwise be redone on everyrealize(), such as quantizing static weights, becomes ordinary input data computed once.- Merged bounds directives (#9410): repeated
bound/align_bounds/align_extentcalls on the same Var now combine into one entry, so call order no longer affects bounds inference. Overwriting a previously set field emits a warning. - Stricter checks:
Stage::fuserejects fusions that illegally reorder dimensions, such as swapping inner and outer RVars (#9186). An explicitfold_storagethat the compiler can't apply is now an error instead of being silently ignored (#9201). - Name collisions (#9145, #9210): Generator
Input<>s and named scalarParams now reserve their names so later Funcs get unique names. If a Func was declared first, you get a clear error instead of a crash in lowering.
Language and IR
- Struct types (#9416):
Type::Structdefines record types with named, optionally array-valued fields, e.g.Type::Struct({{"d", Float(16)}, {"qs", Int(8), 32}}). Inputs and outputs can use packed wire formats, such as blocks of quantized weights, directly, and you read fields withfield(x(b), "d"). Records are not meant for efficient computation: you extract fields first. fmaintrinsic (#8900): the equivalent ofstd::fma, for when you need a real fused multiply-add inside astrict_floatcontext, such as for bit-exact transcendentals.round_half_away(#9573): rounds ties away from zero like C'sround(), whileround()keeps rounding ties to even. It maps to native instructions such asfrintaandfcvtason AArch64, and is exposed in Python ashl.round_half_away.fixed_to_floatintrinsic (#9572):float(x) * 2^-kis recognized as a fixed-point to float conversion, which becomes a singlescvtf/ucvtfon ARM. It saves a multiply per vector when dequantizing fixed-point data, and is lowered back to cast-and-multiply elsewhere.- Better widening (#9195):
FindIntrinsicsnow handles 8-bit integer to float32 widening multiplies, andconcat_bits/extract_bitshave lowerings for the C and Vulkan backends. MultiRamp(#9113): represents a vector whose lanes are an affine function of a multi-dimensional index, i.e. sums of compatible nested ramps. Multidimensional SIMD targets (and matrix multiplies built from nested vectorized loops) used to scalarize easily. Related work includes the AMX fix (#9122), dense load/store canonicalization (#9535), alignment-aware div/mod (#9536), and load recognition (#9261).- Front-end helpers:
- Compiler-developer APIs:
mutate_with/visit_withlet you write mutators and visitors from lambdas that can capture local state (#8877, #8893).Load::make(type, name, index)andStore::make(name, value, index)short forms are available (#9247).- IR nodes have
remakemethods that copy a node with new children (#9245). Block::to_vectorunpacks aStmtinto its non-Block statements (#9384).
Profiling, tracing and debugging
- New profiler (#9157, #9164, #9344, #9368): the report is organized by loop nesting, with allocations and device copies as their own entries. Cheap runtime counters feed recompute ratios and a set of performance warnings about likely schedule problems, which are especially useful to LLMs scheduling Halide code. Inlined Funcs are counted but can't be sampled.
- Profiler control (#9423, #9492): JIT code can access profiler state programmatically.
halide_profiler_set_json_outputsets the JSON path from code, andProfilerScopeaccepts aCallableso repeated calls accumulate statistics. - Halidoscope (#9356, #9413): an interactive tool for exploring traces and profiler output, with a trace view and a profile view. It complements
HalideTraceViz, which shows how a schedule executes but not how much time it takes, and installs via pip. - Tracing:
- Tasks that run in parallel now emit trace events, using a more compact packet format (#9192).
trace_bounds_requiredevents report the region bounds inference requires of each Func, so tools can tell required values from ones computed outside that region (#9506).- Produce/consume events now sit inside the node body, so a skipped stage emits no unmatched begin event (#9505).
JITSharedRuntime::set_trace_fileredirects JIT trace output between realizations (#9516).
HL_DEBUG_CODEGEN:- IR debugging tools:
- The Stmt HTML output has better alignment, predicate placement and rainbow parentheses (#9318).
- lldb, gdb and Natvis share one pretty-printer (#9270).
- An
IRGraphC++ printer emits code that rebuilds anExpr, which is handy for reproducing fuzzer failures (#9042). add_requirement()prints the failingExpr(#9424).- HalideTraceViz accepts abbreviated flags (#9160).
Runtime and tooling
- Thread-pool keep-awake (#9526):
halide_thread_pool_keep_awake(bool)is a refcounted switch that keeps idle workers spinning instead of sleeping, so back-to-back small parallel loops with serial work between them don't pay a wake-up each time. RAII holders exist for AOT, JIT and Python (with hl.ThreadPoolKeepAwake()). no_metadatatarget feature (#9555): omits the_argv()and_metadata()entry points, so pipeline, argument and target names don't end up in the shipped binary.- Runtime symbol prefixes (#9240): runtime import, export and internal symbols can each be renamed with a user-chosen prefix. Several differently-namespaced runtimes can then coexist in one process without sharing state.
- GPU context accessors (#9431):
halide_set_*_acquire_contextandhalide_set_*_release_contextare now available for OpenCL, Metal, D3D12, Vulkan and WebGPU, as CUDA already had. They work on Windows, where weak-symbol overrides don't. - Generator compile cache (#9266): setting
HL_CACHE_DIRmakes generators reuse artifacts when the compiler build, targets, parameters and serialized pipeline are all unchanged. - Dispatch control (#9320):
call_cached_indirect_functioncan now decline to cache a dispatch result. This allows changing allowed CPU features at runtime for benchmarking, or deferring GPU API setup. - Internal linkage (#9556): internal
LoweredFuncs now get internal linkage.
Compiler improvements
Simplifier and bounds
- The simplifier is now idempotent (#8840). It no longer preserves dead
LetStmts (#8899). - Single-use lets are substituted (#9365),
can_prove()can use facts (#9400), and broadcasts are lifted out of pure calls (#9595). - New rules cover:
- Solver improvements for min/max (#9162, #9331, #9457) and solve polarity (#9345).
- Added an upstream simplifier rule verification tool (#9334).
- Added a cheap hash to
Exprnodes to speed upIREquality(#9432). - Fixed a quadratic bounds blow-up at the source (#9448).
- Sliding window bounds are propagated and rewritten correctly (#9150, #9170).
Lowering
- Storage hoisting is now its own mutator (#8832).
skip_stagesworks with CSE, no longer blows up exponentially on chains of let-bound selects, and no longer narrowsloadedby aSelectcondition (#8980, #9147, #9152).- Inlining is faster (#9148), and
inject_host_dev_buffer_copiesis faster (#8996). - No pointless copy-to-host in CPU-only pipelines (#9139).
- Better vector interleaves (#8925), strided-load staging (#8964, #9363), and no deinterleave for div/mod of a ramp (#9584).
specialize()branches are no longer merged away by the simplifier (#9321).
GPU
- Added a check for GPU register crosstalk (#9325, #9450).
- Block-level GPU register allocations are shrink-wrapped (#9343), and colliding register allocations are renamed (#9264).
- GPU shared and heap allocation fusing is deferred to the offload site (#9209).
- Thread barriers are injected only where threads disagree about memory (#9390).
Backends and targets
ARM
- SVE2 is much more complete (many PRs by @stevesuzuki-arm):
- SME2 streaming SVE (#9126).
- Widening FMA (FMLAL) (#8865).
- By-element sdot/udot tests (#9544).
- ARM64 prefetches split at the cache line size (#9599).
x86
- Generic
x86-64-v{N}mcpu with explicit mattrs (#9134). - Better float-of-widening-op codegen (#8976).
- AVX10 detection under SDE (#9135).
- AMX detection requests OS XSAVE permission on Linux (#9199).
- CPU feature detection is shared between the compiler and runtime (#9267).
Other CPUs
- RISC-V: saturating add/sub (#9185).
- Hexagon: more general shuffle flattening and three bug fixes (#8979, #8989).
- WebAssembly: workaround for an LLVM bug in widening loads (#8981).
- Restored the V8 backend (#8961).
- Target feature-flag implications are centralized (#9241).
CUDA
- The driver chooses the register count (#9260).
- More compute capabilities (#9259).
ptxasis told the largest block size (#9284).- Async global→shared copies (#9263).
Metal
- Precompiled shaders (#8814).
gpu_lanes()via SIMD-group shuffles, shared with CUDA (#9514, #9589).- A private temp dir for compiler outputs (#9554).
- Asserts inside kernels warn instead of erroring (#9445).
Vulkan
- More robust loader search paths (#8891).
- Version-based extension detection (#8896).
- Buffer index offsets for arbitrary crops (#8954).
- Allocator and region fixes (#9223, #9224, #9237, #9238).
- A bool UBO layout fix (#9547).
- Boolean min/max SPIR-V lowering (#9494).
Direct3D 12
OpenCL
- Fixed dense 3-lane vector loads/stores (#9576).
LLVM
- Support for LLVM 22 and 23 (#8939, #8892, #8897 and others).
- Halide now pins LLVM versions in-repo (#8942) and tracks LLVM 21.1.8, 22.1.8 and 23.1.4 (#9370).
Bug fixes
roundnow lowers tollvm.roundeven, so it rounds ties to even as documented (#8926).- Fixed double-rounding in double → (b)float16 casts (#8906).
- Fixed
fast_integer_dividefor a signed numerator with denominator 1 (#9271). - Fixed
unique_name()returning duplicate names (#9274). - Fixed these
lossless_cast/lossless_negateissues (#9012, #9166, #9243, #9279). - Fixed
rfactorissues: - Uniquified specialization RVars when domains differ (#9541).
- Fixed alignment tracking and inference (#9104, #9323, #9333).
- Fixed implicit pure defs with RVar args (#9103).
- Fixed
compute_withsliding (#9149). - Fixed vector_reduce simplification (#9031, #9074).
- Fixed infinite recursion in
FindIntrinsics(#9109, #9215). - Fixed LICM re-traversal (#9433).
- Fixed negative-case switch constants (#9290).
- Fixed TBAA metadata for negative indices (#9310).
- Fixed a store clobbering a store of another type (#9577).
- Fixed hoisting of warp shuffles out of if statements (#9513).
- Fixed predicated load/store of one-lane vectors (#8924).
- Fixed an async thread-pool deadlock from semaphore-blocked B-team workers (#9372).
- Fixed a lost wakeup when a stolen serial task is put back (#9534).
- Bounded stack depth from nested parallelism (#9378).
- Size-one parallel loops stay in the task system (#9454).
_Float16is enabled only when the compiler supports it (#9189).- Disabled compiler stack switching in Python 3.14 (#9168).
Security and robustness hardening
A large batch of fuzzer-driven fixes added bounds and size checks to:
- the serialization deserializer (#9208, #9211, #9427)
- image and array loaders: PNG, PNM, NPY/MAT (#9229, #9221, #9255, #9222, #9615)
- HalideTraceViz and HalideTraceDump (#9362, #9358, #9354, #9530, #9616)
- the trace packet parser and
Elf::parse_object(#9220, #9236, #9550) - the tflite and onnx converters (#9551, #9553, #9621)
- runtime utilities:
StringUtils,PointerTable/BlockStorage,log_printf, andhalide_int64_to_string(#9481, #9246, #9549, #9398, #9583)
Build system and packaging
- Added
add_halide_stub(#9093),add_halide_library(ENVIRONMENT ...)(#9545), and lazy loading ofHalideAutoschedulersforPLUGINS(#9304). - Fixed universal2 macOS builds (#9469).
- Fixed per-subtarget entrypoints in multitarget headers (#9315).
- Fixed Windows pip packaging and
libHalideshadowing (#9233). - Fixed PCH visibility (#9455).
- Filtered
std::instantiations out of Halide.dll's exports (#9489). - JIT-compiled CRT calls bind to libHalide's CRT on Windows (#9498).
- Fixed the macOS Homebrew header ordering (#9562).
- Fixed a
getauxvalredeclaration on aarch64 Linux (#9548). - Fixed wabt 1.0.42 compatibility (#9473).
- Added Makefile features to CMake (#9227), and removed
-lzfrom the Makefiles (#9525). - Python bindings:
run_process()replacessystem()(#8940).
Documentation, tutorials and apps
- Docs and tutorial sites migrated to Sphinx (#9347, #9367, #9369).
- All C++ tutorials were ported to Python (#9351). Tutorial text was improved (#9348), and Python-only lessons are rendered (#9408).
- Revised the build documentation (#9006, #9475) and the fuzz-testing docs (#9125).
- Added contribution guidelines and templates (#8975), AI-disclosure requirements (#8625, #9453),
CLAUDE.md(#9275), and Claude skills for scheduling and simplifier rules (#9435, #9447). - New and revived apps and examples:
- The FFT app fix (#9449) and Apple M3 Max matmul tuning (#9183) are also in.
Contributor infrastructure
- Linting: reworked clang-tidy (#8816) with many new checks, plus pre-commit with shfmt/shellcheck, codespell, mdformat, CMake style checks and cmtk formatting.
- Fuzzing: a tiny fuzz-test framework (#9050), a shared expression generator (#9048), a solve fuzzer (#9105), and fuzzers moved to
test/fuzz(#9053). - CI:
- macOS, ARM Linux, x86-32 and Makefile testing moved to GitHub Actions
- a code coverage workflow, actionlint, and
-Werroron CI - a devcontainer
- an automated
halide-llvmupgrade bot - a public nightly PyPI index
New contributors
Welcome and thank you to @stevenraphael, @soufianekhiat, @rajeshgangam, @soma0212, @Nashit-h, @johningve, @gregcotten, @prabalghosh, @arcusbuilds and @parkerziegler.
Full Changelog: v21.0.0...v22.0.0