Xo is experimental. The source is now public on GitHub, or try Xo in your browser.

Xo 0.26 release notes

released 2026-10-22 · tag v0.26.0 · all releases

Downloads: Xo 0.26.0 on GitHub, with archives for macOS (Apple silicon and Intel, signed and notarized), Linux (arm64 and x86-64), and Windows (x86-64, Authenticode signed), and a SHA256SUMS file. Getting started shows how to install one.

Xo against Go

Each release is measured against Go on the same Mac (bench/results/release-vs-go-v0.26.0.md, lines 11 to 22):

  • Run time: Xo is faster on 14 of 20 workloads, within noise on 3, and slower on 3 (conc rwmutex 3.78 times Go’s wall time, conc pingpong 1.51, llvm nbody 1.43).
  • Peak memory of the run: lower than Go’s on all 20 (0.38 to 0.97).
  • Binary size: 1.81 to 1.89 MB against Go’s 2.39 to 2.43 MB.
  • Compile: cold builds 2.0 to 2.4 s against Go’s 1.9 to 2.0 s; a warm build of an unchanged program, 0.42 to 0.64 s against 0.04 to 0.08 s, is the largest gap.

Behavior changes to read before upgrading

  1. xo run DIR runs exactly DIR; a plain directory never walks below it (decision 0345). In a parent of two programs, xo run . used to run the first one it found; it now says which subdirectories hold a program. build, check, test, vet, doc, fmt, fix, parse and audit cover subdirectories only with DIR/..., which stops past 6,000 .xo files or 60,000 entries. Write xo check ./... where xo check . used to cover the tree.

  2. xo build and xo run no longer read _test.xo files (decision 0348). A type error in a test file no longer fails a build, a helper declared only in a test file is not visible to the program, and a use that only tests have is neither fetched nor bound. xo test and xo check report test files’ errors as before.

  3. Fault tracebacks by default. An uncaught fault in a native program prints traceback: and one line per frame down to main, as a hardware fault’s report already did. XO_TRACEBACK=0 restores the one-line report. The binary is unchanged; each fault costs about 0.55 us more, a caught one included.

  4. Embedded DWARF on macOS (decision 0346). Darwin executables carry their debug information in a __DWARF segment instead of a NAME.dSYM beside them, so a copied or packaged binary keeps file and line names in crash reports, tracebacks, the profiler, --race, atos and lldb. --dsym also writes the bundle; -s and --release leave both out (--release --debug-info keeps the segment). Release binaries ship with their DWARF: the darwin archive grows from 23.2 MB to 34.4 MB (+11 MB).

  5. --race is native by default wherever a prebuilt ThreadSanitizer runtime ships (darwin/arm64, darwin/amd64, linux/arm64, linux/amd64, windows/amd64), so it no longer needs clang. In 0.25.2 xo build --race . picked the LLVM backend and failed without clang. Other targets still use LLVM. (decision 0273 addendum)

  6. opt? on an Option in a test block fails the test (decision 0347) with error: missing value: EXPR, instead of being error XO0204. Helper functions and fn literals keep the usual rules.

  7. xo has a memory budget (decision 0349): past three quarters of physical memory, at least 16 GiB (18 GiB on a 24 GB Mac), xo stops with fatal error: memory budget exceeded: ... and the phase it was in, instead of swapping the machine. Builds of xo itself peak at 9.9 to 11.2 GB counted. XO_MAX_MEM sets another budget (off for none); any native program can use it.

  8. Daylight saving gaps: Ambiguity.Earlier and Later were swapped. For a local time inside a spring-forward gap, Earlier now gives the earlier instant and Later the later one, as in an overlap: 02:30 on 2026-03-08 in New York is 01:30 EST with Earlier and 03:30 EDT with Later. A program that relied on the old result swaps the two arguments.

  9. Large Maps branch instead of copying (decision 0291): on the native backends, a Map or Set of 1024 or more entries written while another value shares it no longer copies its lists; the written copy reads the shared lists in place. bench/perf/xo branch: 0.071 of the wall time (65.6 to 4.7 ms), 0.733 of the peak RSS; the HTTP CRUD server within noise. XO_MAP_LOG=1 builds without it.

  10. LLVM builds of large programs split: over 100 MB of IR (xo itself), --backend=llvm splits into modules of about 50 MB with thin LTO and runs as many clang jobs as three quarters of memory allows at 3.5 GB each; the LLVM build of xo peaks at 11.1 GB instead of passing 20 GB. Smaller programs are unchanged (one module, full LTO). XO_LLVM_SPLIT and XO_LLVM_JOBS override.

Also new or fixed

  • --hybrid is deprecated and warns: since the Go compiler’s removal it is a plain Go backend build. --holes builds natively.
  • xo clean -cache [-n] empties xo’s caches (the module cache and the seeds stay).
  • Build outputs: -o DIR writes DIR/NAME; a build writes into a hidden stage and renames into place, so concurrent builds of one output and an interrupted build leave nothing behind; --watch builds go to the user cache; xo init ignores .xo/; -o /dev/null works.
  • Crash bundles go to <user cache>/xo/crash (the newest 100 kept) instead of the system temporary directory.
  • xo run passes a stop request on to the program when not on a terminal.
  • New diagnostics and notes: XO1511 (an embed.Dir literal path it does not hold, with a fix); habit notes and fixes for .or(x), .unwrap_or(x) and friends (opt ?? x) and for a missing use std/json; contract failures print values read inside calls.
  • The Go backend refuses a network program for wasip1/wasm (it spun at full CPU before).
  • xo build --targets=all (and any list mixing architectures) builds each target with its own native backend and names each binary NAME-OS-ARCH (0.25.2 failed, and native builds overwrote each other); --targets=windows/amd64 -o app writes app.exe.
  • Native runtime: a Str or Bytes slice 2 GiB or more into its string read the wrong memory; List.join, large buffers and to_hex over 4 GiB fault instead of returning a cut result; on Linux the 50 us run-next grace uses epoll_pwait2 instead of a 1 ms epoll_wait.
  • Debug information of functions with thousands of overflow checks is shared across fault stubs (a generated 24,000 line function wrote over 4 GiB of assembly), and the SSA builder’s variable reads stopped being quadratic on such functions.
  • XO1201’s note says to build with --backend=go.
  • Runtime: idle workers no longer spin on a run-next slot they may not take yet (an HTTP/2 download server at 0.664x the CPU, Go’s rate); a traceback leak under task.isolate fixed; --race reports name lines again under macOS 26’s atos.
  • macOS: the immortal empty list is back at a page start, which keeps a native program clear of an XProtect false positive (MACOS.SOMA.AUENB); the asynchronous preemption tables moved to __DATA_CONST.
  • The archive’s README starts with getting started for that archive, and a guide for Go programmers covers Option and Result.

Windows

  • Native windows/amd64 programs do their I/O on completion ports (IOCP, decision 0324).
  • xo.exe and xo-gobind.exe are Authenticode signed (Azure Artifact Signing).