Xo 0.25 release notes
0.25.2: the first binary release
Downloads: Xo 0.25.2 on GitHub,
with archives for macOS (Apple silicon and Intel, signed and
notarized), Linux (arm64 and x86-64), and Windows (x86-64, not code
signed yet), and a SHA256SUMS file.
Getting started shows how to install
one.
0.25.2 is the first Xo release with prebuilt binaries. It is 0.25.0 plus
the fixes below. 0.25.1 was tagged but never got binaries: its Windows
xo.exe crashed on every build, because Xo’s COFF writer overflowed the
16-bit relocation count of a section with 65,535 or more relocations.
0.25.2 writes the extended count, as LLVM’s COFF writer does.
Fixed since 0.25.0:
- Windows: sections with 65,535 or more relocations are written
correctly, so the Windows
xo.exebuilds programs again. json.decodeandparse_valuereject a byte after the value, as RFC 8259 and Go do.- Map helpers for
Map[K, Bytes]no longer share a name with theMap[K, Str]ones, which let a cached function call one with the other’s result type. - The arm64 inliner no longer inlines a function into itself after a callback brings its own call in.
- RSA: the secret exponent loop no longer allocates, so the constant-time checks at clang -O2 find no select in it. No secret-dependent select was found, and RSA’s output is unchanged.
- Diagnostics: a Set suggests
addandremove; Time and Duration methods suggest the operators orclock.since(t);m[k]?andm[k] ?? dsuggestm.get(k);m[k] += vsuggestsm.update(k, 0, ...).
Known issue: on macOS, --race reports show their stack frames without
file and line (fixed on main for the next release).
The notes below are 0.25.0’s.
Introduction
Xo 0.25 makes everyday code cheaper without changing what it means:
callbacks no longer copy what they only read, slicing a string no longer
allocates, and a Mutex release hands off to the writer it woke. It also
adds r ?? e => ... for early exits from a Result, lazily computed
constants, any integer type as an index, --race with call stacks on
every native target, and xo vet --cost, which reports costs the source
does not show.
Changes to the language
- Any integer type indexes a List or Bytes (reads, assignments,
get, slice bounds; Str slice bounds too) (decision 0331). x ?? return v,?? break,?? continue, and the Result formr ?? e => body, which replaces the four linematchof an early error exit (decision 0321).- A top-level
constmay call effect-free functions and is computed once, on first use (const ISO: Layout = time.layout(...)) (decision 0332). - Slices of a Str or Bytes are views of the original, with no copy;
clone()makes an independent copy when a small slice of a large input is kept (decision 0330). - Evaluation order: a by-value receiver that is a variable or a field
path is read after the call’s arguments, as a
varreceiver already was:t.size(t.add(v))sees the itemaddpushed (decision 0326). - A function value borrows its arguments: a callback that only reads its element no longer counts it, with no new syntax (decision 0337).
Standard library
timelayouts are named fields ("{year}-{month}-{day}"), checked at compile time when constant;time.layout(text)gives aLayoutparsed once. With a Layout, native formatting takes 2.6 times fewer instructions than with a layout string, and parsing 2.1 times fewer.Str.cloneandBytes.clone.url.parse_queryandmime.parse_media_typecopy their tokens when the input is over 4 KiB, so a kept token no longer keeps a large input alive: 318 MB to 6 MB resident in a probe that keeps one token from each of 300 1 MiB inputs.- A Mutex release yields to the writer it wakes when readers are queued behind it. The CRUD server on linux/arm64: 88.6k to 190.8k requests per second on 3 cores (2.15x) and 70.5k to 136.0k on 6 (1.93x), p99 53.8 ms to 15.6 ms and 65.7 ms to 27.0 ms; unchanged on 1 core.
std/debug: a server’s CPU profile, heap profile, and scheduler state over HTTP behind an auth function (decision 0325).std/metricsandstd/trace.- An idle worker polls the network itself (epoll, kqueue) and runs the
fibers its events ready, as Go’s netpoller does
(decision 0324), and a
with_readshare of a Mutex no longer counts the value’s reference (decision 0334). - ChaCha20 with NEON and the ML-KEM NTT with SSE2.
Tools
xo vet --cost: notes on costs the source does not show: a loop that copies a map’s entries first (XO0907), an element counted up and down each iteration (XO0908), a list update that copies the list each iteration (XO0909), a value over 8 words copied into a closure or an Option in a loop (XO0910), and a stored view of a parameter’s text that keeps the whole input alive (XO0911) (decision 0327).xo bench --ab: interleaved A/B rounds of two programs, or of one under two settings, with each metric’s median, spread, and a verdict (proposed design, built; decision 0340).- A code generator failure names the Xo source (
file:line:col: in function). xo prof URLfetches a running server’s/debug/profile?seconds=Nor/debug/heapand summarizes it like a file.- XO0540, a checker warning: a
Mutex.withclosure that writes its value and then returnsErron another path (the write is undone). --racereports carry the call stack on every native target, and--raceruns on amd64 (darwin, linux); windows/amd64 builds with it, but is not called supported until a Windows run with threads passes (decision 0273).
Performance
- Callbacks:
xs.any(fn(u) { u.name == want })over users with two string fields takes 34% fewer instructions natively and 27% fewer with LLVM; two tasks walking one shared list, 43% and 50% fewer (191 ms to 75 ms natively). - Network servers against Go (linux/arm64): TLS 1.3 handshakes 1.2 to 1.5 times Go’s rate; HTTP/2 1.22 to 1.25 times on one stream and 1.63 to 1.66 on 100. Bulk TLS is behind (0.61 to 0.76 of Go’s), and so is a single HTTP/2 256 MiB download (0.61 to 0.67).
- LLVM builds unroll only loops with Float arithmetic, keeping nbody’s gain (2,308 M to 1,160 M instructions) without 0333’s 12.8% more CPU for xo’s own LLVM build (decision 0338).
- Slices that no longer allocate:
mime.parse_media_type25.6% fewer instructions,multipart.parse16.1%, andsplit,lines, andtrim11 to 14%. - Fiber stacks no longer take transparent huge pages on Linux: the CRUD server at 1024 connections peaked at 2.1 GB of memory before, 47 to 60 MB after (Go 66 to 78 MB).