# Xo 0.26 release notes


**Downloads:** [Xo 0.26.0 on GitHub](https://github.com/xo-lang/xo/releases/tag/v0.26.0),
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](/docs/getting-started/#install) 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](https://github.com/xo-lang/xo/blob/main/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).

