Xo 0.44 release notes
Downloads: Xo 0.44.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. These notes cover everything since 0.41.0.
Xo 0.44.0 fixes a native HTTP server on Linux that stalled on pipelined requests whose sender closed its side right after writing them, brings std/sqlite to windows/amd64 with no C compiler needed, adds destructuring assignment ((a, b) = (b, a), struct and list targets), and makes many byte and string paths faster with 32 byte vector scans on arm64 and amd64. std/cli gains Go flag forms for ports of Go tools, and std/sql’s drivers carry their effects.
Upgrade if you run a native HTTP server on Linux behind clients or proxies that pipeline requests. No change in this release stops a program that compiled with 0.41.0 from compiling, with one possible exception: code that keeps a sql.Db in a closure whose type fixes its effects now names the effect argument (see below).
Breaking changes
None marked breaking. Changes you may notice:
- std/sql drivers carry their effects (decision 0534):
Driver,DriverConn,DriverRows,DriverCursor,Db,TxandRowstake an effect parameterfx, andopen_driverreturnsDb[fx], so std/sqlite’s Db performsfsrather than claimingnet.dial.sql.Dbwithout the argument stays valid; code that keeps a Db in a closure with fixed effects names it,sql.Db[()]for Postgres and the in memory driver, andxo fixwrites that for you. xo checkon a directory inside std (xo check std/...) exits 2 with a usage message;xo test --std-module=Mchecks a std module.- List targets of a destructuring assignment (
[a, b] = xs) report XO0303, aslet’s list patterns do (they were XO0502).
Language
- Destructuring assignment (decisions 0536 and 0538):
(a, b) = (b, a),(q, r) = divmod(x, y)and(p.x, xs[i], _) = tstore a tuple’s elements into existing places, andP{x, y} = porP{x: st.lo, ..} = pstore a struct’s fields, the value first, then each place left to right; XO0539 for a place named twice. - Effects and interfaces (decision 0540): a method whose parameter type leaves its effect arguments out (
next: Next) satisfies an interface method that writes them (next: Next[fx]); it was XO0701.
Fixes
A native HTTP server on Linux answers pipelined requests whose sender closed its side right after writing them; each such connection stalled until its sender gave up. A known issue of the 0.43.0 seed, never in a public release.
A type with a method named
debugordisplaythat takes parameters (a generated API client’sdebug(project, zone, ...)) builds; formatting such a value called that method with only its receiver, which stopped native builds with XO1510, LLVM with a clang type error and Go with a compile error.A stack overflow on Linux and macOS is reported as “fatal error: stack overflow”, as on Windows; on macOS it read as a bus error.
Native backends: a closure that captures more than 32,760 bytes of words builds (a generated GCP client’s service); a method of a struct with an effect parameter called through an interface value compiles;
==on a value holding anhttp.Responseorhttp.Requestcompiles; each was refused before.Go backend: a struct with no fields and
derive Json, two types’ methods of one name with default arguments,returnof an if or match mixingTvalues andErr(e), a std module’s constant read from user code, andxs.contains(x)on a list of a user type all build and run again.Test worlds: a FakeNet handler’s streamed body reaches
Client.sendandClient.opennatively, and a read or write that waits past its deadline ends withTimeouton the Go backend (it reported a deadlock).Cross builds of a program with a
use csource to darwin/amd64 from Linux, or to windows/amd64 with no sysroot, stop with a message that says what is needed instead of a C compiler error.Debugging: lldb on macOS shows a string literal held in a variable correctly in a separately compiled build, and
xo debug’stask Non a task std spawned selects the first frame outside the runtime.std/amqp: an unsettled
sendwith the reader task running no longer waits forever.arm64 programs with more than 128 MB of code no longer crash in reference counting: a retain or release whose object sat in x16 went through a linker branch island that sets x16 (a generated GCP client’s tests stopped with SIGSEGV).
Native backends: a recursive value (a Box) of more than 32,760 bytes of words builds, and structs, tuples and enums of that size compare.
Linux with 64 KiB pages: freeing a 32 KiB heap block no longer discards the pages of the block beside it.
Standard library
- std/sqlite on windows/amd64 (decision 0534 stage 2): linked by xo’s own PE linker, with prebuilt SQLite objects in the release, so a program using std/sqlite builds with no C compiler;
use ccalls on windows/amd64 use the Win64 convention. - std/cli for ports of Go tools:
single_dash_long: truereads-name,-name=valueand-name valueas the long forms (decision 0547),stop_at_positional: trueends the flags at the first positional argument as Go’s flag package does (decision 0556), andbool_flag(..., default:)sets a flag’s value when it is absent. BytesBuilder.write_le(v, n)andwrite_be(v, n)append the n (0 to 8) low bytes of a word in one step, on every backend (decision 0561).- std/json
derive Json(match: fold)decodes object keys as Go’s encoding/json does (an exact key first, else the first field whose key folds equal) (decision 0550). - std/sql’s memory driver takes
CREATE INDEXandDROP INDEX(PostgreSQL’s syntax, one column), so aWHERE column = valueon an indexed column visits only the matching rows. use c: a C function returning a struct of a const byte pointer and a length binds as aBytesresult, NUL bytes kept; a std module’s binding can name its handle types in the module’s own types.- std/amqp and std/amqptest: desired capabilities on links (Event Hubs v2 geo-replication), an event hub’s
georeplication_factor, andBroker.attaches(). - For long-running services:
/debug/schedreportsheap_chunksandheap_cache_bytes; heap profiles carry Go’sbyteslabel, sogo tool pprof -tagfocus=192Bkeeps one size.
Tools
- Windows version information, icons and a manifest (decision 0560):
xo build --versioninfo=FILE.jsonreads goversioninfo’s JSON,--icon=FILE.icoand--manifest=FILEset the icons and manifest, so a project moving from goversioninfo keeps its versioninfo.json. - LLVM builds of a function with more than 8,000 blocks on arm64 compile in about a minute instead of more than 10 (LLVM’s AArch64 machine combiner is turned off for that module).
Speed and memory
- 32 byte vector scans (arm64 NEON, x86_64 SSE2) in std’s byte paths, on linux/arm64 against the release before each step: a one byte
find0.68, Str and Bytes==over 16 bytes 0.73,Str.countof a newline 0.741,findof a common first byte 0.347,Str.quote0.542,equal_foldon 4 KiB 0.185 (0.165 of Go’s). - Crypto on arm64 and amd64 vector units: SHA-256 0.884 of before on linux/arm64 (0.79 on linux/amd64), SHA-1 0.884 (0.86), SHA-512 0.965.
- Encoders: std/cbor’s deterministic marshal of a derived struct 0.442 of before, and
cbor.marshalof a derived type writes its encoding directly, 0.46 of that (2.8 ms against Go’s 4.3 ms),JsonWriter’s strings 0.75, JSON decode of 10 KB documents 0.827 wall and 0.785 max RSS on linux/arm64, std/sql’s memory driver ORDER BY 0.574, labelled std/metrics counters 0.760 (a small List literal argument no longer allocates). - Memory: a List or Str growing past 1 MiB reuses its last mapping (JSON decode on linux/amd64: max RSS 45,352 to 12,092 KB, Go 13.9 MB); a List of word-sized elements starts with room for 2, so xo’s own build peaks 4.4% lower.
- Toolchain: a warm build on Linux no longer copies its part objects (an unchanged hello wrote 3.7 MB to the cache every build); the native backends’ self build peaks at 6.2 to 6.4 GB mapped instead of 6.8 to 6.9 on linux/amd64; std’s parses come from the build cache.
- More std paths: std/path’s
dir,base,extandsplit0.918 of before (clean0.934,join0.942), an AES key schedule 0.717, std/netip’s IPv4 parse 0.816, on linux/arm64. - Long-running services on Linux give the system the pages of free heap blocks of 16 KiB and over once they stay free 30 to 60 s (decision 0533).
- Go backend builds of large generated packages take less memory: azure armnetwork compiles in 8.1 GB and 61 s instead of 9.1 GB and 68 s.
Known issues
- On macOS, once std’s prebuilt variants are in the cache, a program’s default build compiles separately and its debug map names the object files lldb reads in the build cache (
~/Library/Caches/xo/backend/dbgobj/), so the binary carries the builder’s home directory despite--trimpath. Debugging works, but a binary you hand to someone else names your home directory. Build withXO_SEPCOMP=0(or--release) for a binary to share. Decision 0563 settles it. The release’s own binaries are built whole and hold no build path.
Xo against Go
Measured before the cut on the release candidate (b0ee4622c, Go 1.26.1):
- macOS arm64 programs: ahead of Go on 15 of 20 run rows by wall (an interpreter 0.31 of Go’s time, collection iteration 0.27, a mutex 0.30, spawn 0.46), level on four, one slower (nbody 1.026); every row at 0.32 to 0.84 of Go’s peak memory.
- Tests of std (
xo test --std-moduleagainstgo test): json cold 0.395 of Go’s time and 0.271 after an edit; base64 0.671 and 0.705. After an edit xo’s test run peaks higher (base64 1.68 times Go’s memory, json 1.22). - Builds: cold 1.04 to 1.41 s against Go’s 1.44 to 1.51 s, an edit 40 to 100 ms against 100 to 110 ms; binaries 1.45 to 1.63 MB against 2.39 to 2.43.
- HTTP server (linux/arm64):
/helloserves 1.29 times Go’s requests per second at 32 connections and 1.83 times at 256; a JSON response (/alloc) 5.99 and 3.18 times, and at a fixed 20,000 requests/s its p99 is 0.22 of Go’s. Every row is level with 0.41.0. - linux/amd64 (micro13,
--release, 9 rounds): ahead of Go on six of seven programs (JSON 0.29 of Go’s time, string building 0.40, map1M 0.69, fib 0.92, both sorts 0.94), spawn100k level; JSON’s peak memory is now 0.70 of Go’s (0.21 of 0.41.0’s), map1M’s 1.42 times. Level with 0.41.0 on every row but JSON (0.725), within 2%.