Skip to content

Releases

v0.9.2

v0.9.2 Pre-release
Pre-release

Choose a tag to compare

@StefanVonRanda StefanVonRanda released this 12 Nov 12:01

Tycho 0.9.2 — pre-1.0, no stability guarantees.

0.9.2 expands the core library to cover the Odin core capability set and adds
Android and iOS compiler target gates. This is capability parity, not API parity:
Tycho keeps its value semantics, implicit arenas and Result model, so Odin
code does not compile unchanged.

Core library

  • Compression — PPMd7/PPMd8, PKZIP Shrink/Reduce/Implode, Deflate64,
    shoco, BCJ2, delta, BCJ filters for x86/ARM/ARM Thumb/ARM64/PowerPC/SPARC/
    IA64/RISC-V, and swap2/swap4.
  • Encoding and assets — HxA v3 reader/writer, BNG decode/encode, Qt .qm
    catalogs, locale data and native Lua %b/%f/non-greedy patterns.
  • Platform and runtime — leap-second TZif records, epoll/kqueue/IOCP
    completion loops, deterministic prof sampling with Spall traces, terminal
    cursor queries and Windows tty fallbacks, thread-local storage and a
    synchronous OS-thread runner.
  • C interop — typed C function-pointer invocation in core:dynlib, and
    named C-to-Tycho callbacks through compiler-generated C-ABI trampolines.

PPMd and the PKZIP legacy codecs are vendored public-domain C (7-Zip SDK and
hwzip). Deflate64 uses zlib's infback9. BNG uses the Unlicense reference
codec. The vendored codecs add no runtime library dependency.

Platforms

Prebuilt archives are attached for Linux x86-64, Windows x86-64 and
macOS arm64. The macOS archive is built natively on Apple Silicon and
smoke-tested there; it is not a cross-compiled binary.

Android and iOS are compiler target gates, not end-user platforms in
0.9.2. make android-check and make ios-check build and run dependency-free
corelib tests for those targets; no app archive, APK, Xcode project, JNI binding
or signed-device run is shipped.

Known limitations

  • C-to-Tycho callbacks accept named functions only. Callback parameters and
    results are limited to scalars, ptr and void; closures and function
    values are rejected.
  • mem, relative, runtime reflect, raw sys, c, odin, rexcode, SIMD
    intrinsics and slice remain explicit non-goals.
  • Tycho is pre-1.0. The language surface and corelib API may change before 1.0.

Prebuilt archives and checksums are attached to this release.

v0.9.1

v0.9.1 Pre-release
Pre-release

Choose a tag to compare

@StefanVonRanda StefanVonRanda released this 10 Nov 20:14

Tycho 0.9.1 — pre-1.0, no stability guarantees (see the README for
what that means in practice). Prebuilt binaries are attached, so you can try the
language without building from source.

0.9.1 is the single-compiler, tooling and measured-performance checkpoint.
The language surface is unchanged. The historical C compiler is gone, the
self-hosted compiler is the only implementation, user-program builds are
substantially faster, and the remaining benchmark losses were closed or reduced.

One compiler

  • src/tychoc.c is deleted. Its final source remains in git history at
    c7d46528ac6efcf9dc93359d14d98bedcb7ddbad. Historical citations resolve
    against that revision; the reject classifier reads the frozen
    compiler/reject_sites.json; single-compiler-check fails if the file
    reappears.
  • tycho-debug bt is source-aware. Demangled pkg.name frames, .ty
    file:line, with runtime glue and native frames tagged.

Compile speed

  • Optimized user builds use LTO. hello.ty drops from ~1.6 s to ~0.53 s;
    compiler/main.ty from ~26.6 s to ~6.3 s on gcc 16.2.1. Generated units under
    1 MiB use -flto; the compiler unit uses -flto=auto. Debug builds
    (-O0 -g) keep the plain path.
  • The development loop is explicit. make check-fast runs the fixture,
    parser, citation, corelib, debugger, surface and doc-count gates in ~3 min;
    make parse-check is a real target and is in CI.

Performance

Large-size rows, same checksum per workload:

row 0.9.0 / before 0.9.1 C
json_tree time 84 ms 51 ms 49 ms
json_tree RSS 80.9 MB 81.7 MB 103.6 MB
lru RSS 20.2 MB 14.7 MB 12.0 MB
lru time 244 ms 226 ms 233 ms
trie time (GCC) 116 ms 89 ms 79 ms
dispatch expert ~24 ms 12–13 ms 13 ms
binary/arena (GCC) 423 ms 377 ms 292 ms
binary/arena (clang) — 516 ms 504 ms

The wins are structural: inlined string byte/equality paths with out-of-line
slow paths, thin 8-byte function values for programs that cannot create
closures, NULL-leaf recursive two-variant enums, small-map linear scans, and
map compaction at 1/8 dead. The remaining lru memory gap is the generic
map's duplicate key/value/index arrays; binary/arena is at clang parity and
still spends GCC cycles in recursive arena allocation.

Corelib

  • Checked siblings for the last sentinel APIs: io.write_text,
    io.write_lines_checked, io.flush_stdout_checked,
    net.udp_{bind,send,read}_checked, path.safe_join_checked and
    path.resolve_under_checked.
  • UDP read distinguishes a legal zero-length datagram from timeout and failure.
  • All additions are recorded in surface.lock; no corelib function was removed.

Verification

On the 0.9.1 tree:

  • make ci N=0 — green, recorded for the exact tree
  • make test — 1,157 fixture jobs, 0 failed
  • make corelib — 49 test trees, all green
  • make fuzz N=200 — 200/200
  • make fuzz-reject N=200 — 200/200
  • make fuzz-leak N=150 — 150/150
  • make fuzz-tsan N=200 — 63 ran, 137 skipped by design, 0 failed
  • make bench-suite-gate — green on GCC and clang results
  • make release-check — byte-identical native archives
  • scripts/release.sh v0.9.1 --mingw — Wine smoke-tested Windows archive

0.9.1 is pre-1.0 and there are no stability guarantees: anything here may
change before 1.0. The two 1.0 blockers are still a real program written by
someone other than the author, and an external security review; see
docs/internals/1.0-readiness.md.

v0.9.0: Keep borrowed heap values out of fresh push adoption

Choose a tag to compare

@StefanVonRanda StefanVonRanda released this 26 Oct 18:36

Tycho 0.9.0 — pre-1.0, no stability guarantees (see the README for
what that means in practice). Prebuilt binaries are attached, so you can try the
language without building from source.

0.9.0 is the managed-performance and compiler-parity checkpoint. The
language surface is unchanged; this release makes Tycho competitive with the
best managed implementations by measurement, closes the tracked gap between the
C bootstrap and the self-hosted compiler, and removes the remaining CLI hash
bottlenecks.

Managed performance

The generated suite covers 18 workloads at three sizes, with Go, Java and
Swift ports where the workload is meaningful. The release gate requires every
row to be within 1.10x of the best managed time or memory on at least one
backend, with a 1.15x self-regression limit and a repetition-noise check.
C and Rust are reported as native diagnostics, not release blockers.

Using large-size geometric means under GCC, Tycho measures 0.40x Go time and
0.50x Go memory; against Java the figures are 0.45x time and 0.05x
memory
, and against Swift 0.18x time and 0.51x memory. The clang
report is similar (0.37x Go time, 0.50x Go memory). All compared rows pass the
managed gate.

The suite also gained specialized rows and managed ports:

  • specialized trie, dijkstra, lru, interp and json_tree rows now have
    Java and Swift peers;
  • Rust/C service, SQLite, latency and specialized rows are measured for
    context;
  • binary/arena, dispatch/expert and maptree/arena remain explicit control
    variants without managed ports.

Memory-heavy rows improved substantially:

workload before 0.9.0
trie 132.7 MB 76 MB
json_tree 107 MB 81 MB
gcscan 372 MB 127 MB
invindex growth rows — 150–194 MB

Compiler and runtime

  • Reference-compiler parity is complete for the tracked features. The C
    bootstrap now has the closure ABI, simple-place calls, counted/while
    presizing, loop-scope elision, inline scalar-payload enums and nested push
    destination inference. Its generated suite is checksum-clean. The saved
    benchmark reports still measure the self-hosted tychoc1 as the optimized
    path.
  • Inline scalar-payload enums are emitted as a packed tag plus variant
    union in both compilers instead of a pointer to an arena cell. Recursive,
    generic, fieldless, package-mangled and heap-payload enums keep the pointer
    layout. TYCHOC_NO_INLINE_ENUM=1 restores the old representation for
    measurement.
  • Closure environments and call descriptors are inlined for small
    functions; loop-local arrays move into returned push destinations; counted
    for and canonical while builders are presized from their bounds. These
    remove geometric growth and closure-scope allocations from the hot rows.
  • Map and list churn is now competitive: read-only map lookups borrow,
    unshared map writes avoid a copy, integer push has an inline fast path, and
    integer map keys use a collision-free permutation hash. The winagg row
    dropped from ~165 ms to ~84 ms against C ~97 ms.
  • JSON and map layout became flatter: composite map headers are smaller and
    backed by a nullable data block; string map keys and repeated JSON keys are
    interned; the fast parser builds a compact field spine directly; repeated
    key/value strings are sliced once. Json.JObj now carries a single
    [JsonField] array; see Compatibility.

CLI and real programs

  • tycho-hash no longer deadlocks or grows with the file count. A producer
    task feeds bounded 32-slot queues while the main thread drains results, which
    preserves the 96-file stress fix without scaling memory with the tree size.
  • Whole-file hashing is hardware-accelerated when available. The runtime
    uses SHA-NI first, optional OpenSSL SHA256 next, and scalar C otherwise.
    The pure Tycho streaming implementation remains the portable API.
  • On a deterministic 512-file / 128 MB tree with eight workers, Tycho now
    hashes in 0.01 s / 10.8 MB versus Go's 0.01 s / 7.1 MB, down from
    74.7 MB before the channel fix.
  • The production multi-worker server serves static files at 118.8k
    requests/s / 3.5 MB
    , versus Go http.FileServer at 84.9k / 17.7 MB.
    The minimal single-loop fixture is intentionally not the recommended server
    shape; it does not scale with connections.
  • The real-program pass also covers concurrency rows, SQLite (dbquery within
    ~2% of C and ahead of the managed ports), and large JSON documents (tied with
    Java on time and far below it on memory).

Compatibility

No language syntax or value-model change. surface.lock gained only the
additive sha256.hex_bytes. The one known breaking corelib change is
corelib/json's object representation:

  • before: JObj([string], [Json]) with parallel key/value arrays;
  • now: JObj([JsonField]), where JsonField holds k: string and v: Json.

Code that constructs or pattern-matches JObj directly must be updated. Code
that uses the parser, serializer and accessors is unaffected. sha256.hex_bytes
is additive.

Verification

  • make test — 1163 passed
  • make corelib — 49 ok
  • make fixpoint-check — gen2.c == gen3.c
  • make bench-suite-gate — every compared managed row within 1.10x
  • make external-check — deterministic hash reports, worker counts and error
    paths
  • make docs-fences — 245 snippets, 0 failures
  • make fuzz-quick — 60 seeds across native and ASan/UBSan builds, 0 failures
  • make asan-self — 1171 compiler programs, 0 failures
  • GCC and clang backend reports; Windows cross-build check

Known gaps

  • CLI hash process memory is ~1.5x Go's. SHA throughput is no longer the
    cost; the remaining working set is the per-worker file buffer and runtime
    state.
  • Minimal single-loop server scaling remains a documented limitation. Use
    the production multi-worker server.
  • Native C/Rust still lead on some pointer-heavy or recursive workloads,
    most visibly json_tree, trie and dijkstra. This is a diagnostic, not a
    managed-language loss.

0.9.0 is pre-1.0 and there are no stability guarantees: anything here may
change before 1.0. See CHANGELOG.md for the accumulating record.

v0.8.9

v0.8.9 Pre-release
Pre-release

Choose a tag to compare

@StefanVonRanda StefanVonRanda released this 21 Oct 17:25

Tycho 0.8.9 — pre-1.0, no stability guarantees (see the README for
what that means in practice). Prebuilt binaries are attached, so you can try the
language without building from source.

0.8.9 is the 3D and first-GPU checkpoint. The value model and the
O1–O7/FFI work from 0.8.8 are unchanged; this release adds a pure software-3D
library, three 3D callers, and an explicitly experimental SDL_GPU backend.

Software 3D

  • core:gfx3d is a pure Tycho camera and flat-shaded triangle rasterizer.
    It exposes Vec3, Camera, Target, SVert, Tri, Mesh and Place;
    look_at, project, draw_tri, draw_quad, draw_box, clear, mesh,
    add_tri, add_quad, add_box and draw_mesh.
  • Near-plane clipping and corrected barycentric inverse-depth interpolation are
    pinned by corelib/test/gfx3d, including the depth-ordering case that broke
    the first cube renderer.
  • Mesh holds local-space triangles; Place applies a caller-built
    right/up/forward basis, origin and uniform scale without copying the mesh.

Three callers

  • Vector Vault — first-person collectathon, z-buffered triangles, portal,
    timer, pause/restart, resize and a standalone dist/ package.
  • Orbital Gallery — orbit camera and gallery scene, exercising a different
    camera model.
  • Rotor Run — third-person drone game using Mesh/Place, a multi-part
    body and independently spinning rotor, follow camera and pickups.

All three use the same core:gfx window/input/audio/font path, and the software
renderer remains available.

GPU backend — experimental

  • core:gfxgpu creates an SDL_GPU device, pipeline, depth target and
    swapchain for a core:gfx window.
  • Shaders are committed SPIR-V and translated to the native format by
    SDL_shadercross (scripts/build-shadercross.sh builds the pinned runtime).
  • Vector Vault and Rotor Run upload world-space meshes, use a camera MVP vertex
    uniform, draw depth-tested scenes, and blend font-atlas HUD overlays directly
    on the swapchain. The same code has run on Linux/Vulkan and macOS/Metal.
  • The backend is deliberately marked experimental: the shadercross runtime
    is a separate build dependency, Windows GPU validation is not complete, and
    at 480×270 internal resolution it is not yet faster than the software path.
    Software remains the fallback.

Compatibility

No new value-model or escape-hatch language change. surface.lock moved by
addition only. The strings.parse_int, eprintln, path.real and FFI changes
from 0.8.8 remain in force.

Verification

  • make corelib — 49 ok.
  • make parse-check — 1528 files.
  • make surface-check — 486 corelib functions.
  • Three software 3D callers and the GPU probe pass headless/X11 checks.
  • Valgrind and ASan are clean on the software callers and the gfx/image smoke
    tests. GPU-driver leak coverage is not claimed on Linux.
  • The full local CI and release archive checks are run before tagging.

0.8.9 is pre-1.0 and there are no stability guarantees: anything here may
change before 1.0. See CHANGELOG.md for the accumulating record.

v0.8.8

v0.8.8 Pre-release
Pre-release

Choose a tag to compare

@StefanVonRanda StefanVonRanda released this 21 Oct 12:35

Tycho 0.8.8 — pre-1.0, no stability guarantees (see the README for
what that means in practice). Prebuilt binaries are attached, so you can try the
language without building from source.

This release combines the seven pieces of real-program feedback from the
tycho-dupe probe with the O1–O7 optimizer sweep, POD struct FFI, and the first
complete game path.
It adds no references and no escape hatches. The language
surface is unchanged in shape; surface.lock moved by addition only.

Breaking

  • strings.parse_int is now the strict parser. It returns
    Result(int, IntErr) and refuses empty input, trailing garbage and
    out-of-range values. The old fail-open scanner is now
    strings.parse_int_prefix; parse_int_checked is gone. Every call site,
    test, example, tool and doc was updated. This is the one change a 0.8.7
    program is likely to notice.
  • eprintln(s) is a builtin. It writes s and a trailing newline to
    stderr. eprint(s + "\n") still works.
  • path.real is implemented through real_checked. Its fail-closed
    behavior is unchanged; the new spelling returns a named PathErr.

What the probe changed

  • core:io can now classify a file. FileKind, Stat, stat, lstat
    and same_file (device + inode on POSIX, handle identity on Windows) give a
    filesystem tool one syscall for kind, size, mtime and identity. Symlinks,
    hard links, FIFOs, sockets and devices now have answers instead of a C shim.
  • core:path can say why a resolution failed. real_checked returns
    NotFound / Denied / TooLong / BadPath / Failed; real remains the
    fail-closed convenience.
  • path.quote gives a POSIX shell-safe single-quoted path for text reports.
  • tycho-dupe is in tools/tycho-dupe, with make dupe-check. It is the
    probe program, not a demo: content hashing, filesystem identity, JSON output,
    error paths and both compilers.
  • The copy/borrow boundary is written down. The reference, guide and spec
    now say exactly which values are borrowed and which copy: heap descriptors
    share storage; [N]T, vector[N]T and inline-array structs copy the whole
    value.
  • inout forwarding is documented with a compiling example.
  • tools/tycho-dupe/FRICTION-OUTSIDE.md records what went right and what
    remains, with status notes for each finding.

FFI

Plain scalar-field structs can be inout out-parameters. A non-packed,
non-aligned struct of numeric fields now crosses as a normal S_Name *; the C
function fills it in place. Use fixed-width fields (i32, u8, f32) to match
the C header. The same POD struct can cross by value and as a return;
a dynamic [POD] parameter crosses as (const S_Name *, len), borrowed for the
call. This covers SDL_Rect/SDL_FRect/Vector2-style APIs without shimming the
values themselves. Strings, maps, handles, packed/align structs and non-POD
composites remain rejected — use --shim for those. tests/ffi covers the round
trips and all rejection paths. The windowed examples/gfx demo passes
SDL_FRect and SDL_Rect by value and a [FVec] (SDL_FPoint layout) array
to SDL_RenderLines, exercising the real SDL layouts on the GPU path. C unions
stay non-crossable; the shim decodes SDL_Event into a tagged POD Event.
The same example queues signed-16-bit PCM through an SDL audio stream, so sound
needs no callback ABI; SDL_AUDIODRIVER=dummy makes the test lane silent. Its
asset manifest (assets/manifest.json) is parsed with core:json; each PNG is
decoded with core:image, uploaded as an SDL texture, and looked up through a
[string: int] ID cache. One scoped TexSet handle owns every texture;
close(texset), close(audio) and close(win) release the textures, stream
and window in order. core:image frees its per-image handle header and keys
the double-free guard by a monotonic live-set ID, so loading the PNG atlas does
not retain one header per decode. Individual typed handles still cannot
live in collections, but a collection handle covers the homogeneous cache. A PNG glyph atlas is decoded by core:image, uploaded as an SDL texture, and
drawn glyph-by-glyph for SCORE n and GAME OVER, with no SDL_ttf. The reusable
layer now lives in core:gfx (corelib/gfx, auto-linked shim); examples/gfx/pong
is a second caller using the same package alongside Snake. present is
parameterless, with overlays drawn through the separate draw_lines call;
key-to-action mapping and centered text measurement live in core:gfx. A
gfx.Clock provides fixed-step simulation with a capped catch-up, so both games
simulate by time rather than frame count. Value Sprite/Anim types and
draw_sprite/anim_frame/draw_anim add sprite-sheet animation; the examples
use four-frame apple and ball sheets. A value Mixer with mixer/add/
finish/play_mix mixes overlapping PCM in Tycho; both games play a two-note
score jingle without audio callbacks. repeat/play_loop queue finite PCM
loops for music; the caller re-queues when it wants more. Snake now has a title
menu, three speeds, pause/restart, a persistent high score and letterboxed
resize, with package.sh producing a runnable directory.

Compiler optimizations

O1 ships in 0.8.8: immortal allocation + literal lifetime elision. String
literals now carry a negative length header; the runtime masks it, returns an
immortal literal unchanged on copy, and skips it on recycle. Every copy, push
and assign path inherits the elision without a compiler branch.

On 6,000,000 literal pushes into a [string] field: 207,460 KB / 61 ms →
114,056 KB / 28 ms
(median of five), byte-identical output. The result is in
bench/opt/RESULTS.md.

Making O1 real also exposed a latent escape-analysis bug: return Env(m) now
marks m as escaping in both compilers, so a mutated map/array local returned
through a zero-cost newtype no longer keeps its storage in a scope that is
freed on the way out. tests/newtype_agg is the regression.

The O1–O7 optimization phases are all in 0.8.8; O1 was the prerequisite for
O6, and O7 closes the allocation gap on the flat array pipeline.

O2: read-only pass-by-pointer in the reference compiler. It passes
non-heap fixed arrays of four or more elements as const T* on direct calls.
On 5,000,000 calls through fn first(xs: [256]int): 140 ms → 66 ms (median
of ten, -O2 -fno-inline), same RSS. Function values, lambdas and spawns
bridge the ABI; FFI, inout and sink are unchanged.

The native archive ships tychoc1, the self-hosted compiler. It already
lowers [N]T to a 24-byte descriptor, so it never copied the 2 KB payload:
the same fixture measures 10 ms with the packaged compiler. O2 therefore
closes the reference compiler's ABI-copy gap and brings ./tychoc to the
representation the shipped compiler already had; the packaged binary's
performance is unchanged.

O3: last-use moves into push. A loop-local declared at the current loop
depth is a fresh buffer each iteration. When its only read is push(dst, x),
the declaration is promoted into the destination array's arena and the push
stores it without a deep copy; a heap rebind and a direct sink call get the
same relaxation. A local from outside the loop, or a push into a caller-owned
sink array, still copies. On bench/opt/movebuild (500,000 four-string loop
locals): tychoc1 91 ms → 79 ms, ./tychoc 96 ms → 87 ms, same RSS.
tests/move_loop_local.ty is the regression for both sides of the boundary.

O4: borrowed in-scope slices. A string slice used as a call argument is a
16-byte (parent, length) view instead of a copy. Binding, returning, push,
map keys and FFI still materialize through the copy seam. On 100 slices of a
12.8 MB string: tychoc1 33 ms / 42.9 MB → 7 ms / 30.6 MB, ./tychoc
32 ms / 43.0 MB → 6 ms / 30.7 MB. tests/pkg/slice_view covers both the
view and the materialization sides.

O5: destination passing for t = f(t, ...). When t is read once in the
RHS and immediately rebound, both compilers generate a variant of f whose
matching parameter is owned by the callee. Inside a return the variant may
move a field or match payload of the old value into the result instead of
deep-copying it, and a recursive call that consumes the changed child selects
the same variant. The result is built in the assignment target's arena, so a
moved field and the result share a lifetime; a call whose argument is still
live, or whose result goes anywhere else, keeps the copying function.
TYCHOC_NO_DEST_PASS=1 is the control.

A 12,000-insert binary tree drops from 2,252,644 KB / 500 ms to 8,908 KB /
3 ms
in tychoc1 (./tychoc: 2,252,484 KB / 493 ms → 9,112 KB / 3 ms). A
16,384-leaf editor rope edited 10,000 times through t = set(t, i, v) drops
from 8,966,776 KB / 1,800 ms to 7,584 KB / 3 ms (./tychoc: 8,966,596 KB /
1,783 ms → 7,748 KB / 4 ms). Output is byte-identical. tests/dest_pass.ty
pins the unique, shared and snapshot cases; the fixtures are under
bench/opt/treeupdate and bench/opt/editorupdate.

O6: conservative shared-bit CoW / path copying. A same-arena b := a of a
composite map, or of a struct whose mutable fields are maps, shares the map and
sets a conservative shared bit; the first write through either copy duplicates
the map's arrays and marks nes...

Read more

v0.8.7

v0.8.7 Pre-release
Pre-release

Choose a tag to compare

@StefanVonRanda StefanVonRanda released this 18 Oct 16:35

Tycho 0.8.7 — pre-1.0, no stability guarantees (see the README for
what that means in practice). Prebuilt binaries are attached, so you can try the
language without building from source.

This is a correctness release: it refuses programs 0.8.6 accepted, and
closes the 90-finding defect hunt of 2026-09-25.
Every refusal names the
rule it enforces, and almost all of them are programs that were already wrong:
they wrapped a value silently, emitted C that did not build, or read memory
they should not have. There are no language features and no surface removals;
surface.lock moved by addition only (seven corelib functions joined it).

Programs now refused

  • An out-of-range literal wherever it adapts. x + 300 at a u8, a 300
    passed to a variadic or UFCS parameter, a lambda body, a m.get default, a
    subscript assignment, a value match arm, a send, or the broadcast scalar
    of a sized-int array wrapped silently and printed the wrapped value. Every
    site now says 300 does not fit u8 (its range is 0..255).
  • An empty literal [] for a [N]T or vector[N]T. The reference
    compiler emitted an uninitialised C aggregate; the self-hosted one built a
    length-0 "fixed" array. Now: a fixed-size array of length 2 needs 2 elements, got 0.
  • A [N]T whose N is an int const losing its length in the
    self-hosted compiler: wrong-arity literals, push onto it, zero or negative
    N, and mismatched fixed-array arguments all compiled and ran with the
    wrong len.
  • An f-string hole holding more than one expression. f"{price:.2f}"
    parsed the first expression, discarded the rest, and printed the bare value.
  • The identifier prefix Ztycho_. Compiler temporaries shared the user
    namespace, so a local named _fc0 was silently replaced by the compiler's
    own and the program printed a wrong answer. The prefix is reserved now.
  • A handle free: destructor returning an aggregate, which passed the
    check and failed later inside cc.
  • An empty -o "" or a trailing bare -L/-I on the compiler command
    line.

Fixed

  • The defect hunt is closed: 90 findings, all fixed. Eleven finder agents
    on one surface each, every finding handed to a verifier told to refute it.
    The ledger is
    docs/internals/audit-2026-09-25.md.
    Beyond the refusals above, the compiler half:
    • Division by zero traps cleanly in the self-hosted compiler instead of
      SIGFPE/SIGILL, on every sized integer and element-wise array arithmetic.
    • An if/elif chain and a long + chain emit flat. The self-hosted
      compiler needed 5.3 GB for 800 arms and died at 1250; the reference
      compiler segfaulted near 7000 arms and clang refuses 256 bracket levels.
      A 1400-term sum now emits at depth 4 from both compilers.
    • Compile time on nested calls is no longer exponential: nested generic
      method chains, 900-struct program_in copies, one long f-string, lambdas
      among many locals, and the reference front end's quadratic lookup.
    • A constant array literal is a static table plus memcpy, not one
      store per element — a 15,625-element table took gcc 21 s.
    • Enum variants named EOF, NULL, default or while build, under
      prefixed C names.
    • A packed field passed as inout goes through an aligned local in
      both emitters, and no longer draws a -Wunused-value warning in the
      user's build.
    • Composite float map keys hash +0.0 and -0.0 alike; a map held two
      ==-equal keys.
    • The reference compiler's heap use-after-free on nested generic
      instantiation is gone
      , with read_file on pipes, the error-recovery
      depth counter, __ type names, clamp naming, --symbols on invalid
      programs, and cross-package suggestions fixed alongside.
  • Appending through an inout string in a helper no longer falls back to
    quadratic concatenation.
    Both emitters pass a per-binding append state
    that grows in the owner's arena (tests/inout_append.sh bounds the growth).
    The self-hosted compiler's redundant AST staging is gone with it.
  • Size arithmetic on untrusted counts is checked before it allocates:
    reserve on composite arrays and channel(T, cap) with a huge capacity
    could wrap to a tiny allocation and write past it. Both abort by name now.
  • Overwriting a heap value in a long-lived map reclaims the old value.
    tycho-kvsrv grew ~1 KB of RSS per PUT to the same key without limit.
  • Enum payloads recycle at their exact size, not sizeof(*v), in both
    emitters.
  • Negative literals adapt like positive ones in the self-hosted compiler
    (x: f32 = -0.5 compiled under one compiler and not the other); float
    to_u8..to_i64 are total; a u64 shift past 2^63 is zero per spec.
  • The shipped compiler calls itself tychoc instead of tychoc1, says
    cannot open <path> for a missing source, and writes --emit-c output to
    stdout with no -o.
  • core:tls verifies the hostname with X509_VERIFY_PARAM_set1_host
    rather than the deprecated call, and core:datetime bounds the libc TZ
    fallback on macOS.

Install

Download the tarball for your platform below, verify it, and unpack it:

tar xzf tycho-<version>-<os>-<arch>.tar.gz
cd tycho-<version>-<os>-<arch>
./tychoc examples/hello.ty && ./examples/hello

tychoc writes the binary beside its source unless you pass -o, so the
program built from examples/hello.ty is examples/hello.

The core library ships inside the tarball, beside the compiler, so there is
nothing to configure. You still need a C compiler (cc) on your PATH, because
Tycho transpiles to C: gcc, or clang 15 or newer. On macOS that is the Xcode
command line tools. Each tarball's SHA-256 is published alongside it.

What this release does not have

There has been no third-party security review, and the FFI
(foreign-function interface, extern fn) boundary is
unsafe by design. See SECURITY.md, and
docs/internals/audit-brief.md if you are
willing to be one.

Two things are unverified, and you may hit them:

  • This Windows archive has been run under Wine, not on Windows. In a
    debian:trixie container, the staged tychoc.exe started under Wine and
    emitted C for a program using core:strings found beside the binary. That
    is the whole Windows verification: the 23-leg content gate ran over the
    native Linux archive only. The toolchain itself has passed its suite on real
    Windows guests, 289/289 on both windows-x86_64 and windows-arm64
    (make platform-check), but that was an earlier build, not this tarball.
  • Server shutdown on Windows has never been observed on Windows. The code
    reads sound: a worker re-reads the shutdown flag at least ten times a second,
    and the console handler shuts down the listener and every registered
    connection. What is untested is whether WSAPoll returns early on a socket
    that has been shut down, and whether a blocked accept is released. Expect
    wind-down within the 100 ms poll tick. Treat anything faster as unproven.
  • There is no darwin-arm64 archive this time. The 0.8.6 Mac archive
    stands; this release attaches Linux and Windows only, both built from the
    tagged tree. A Mac build needs a Mac.

Status

0.8.7 is pre-1.0 and there are no stability guarantees: anything here may
change. ROADMAP.md lists what 1.0 requires, and
the blocking item is not engineering. Nobody outside this repo has written a
real program in Tycho yet. If you write one, the friction you hit is the most
useful thing you can send back.

v0.8.6

v0.8.6 Pre-release
Pre-release

Choose a tag to compare

@StefanVonRanda StefanVonRanda released this 25 Sep 12:31

Tycho 0.8.6 — pre-1.0, no stability guarantees (see the README for
what that means in practice). Prebuilt binaries are attached, so you can try the
language without building from source.

This release refuses programs 0.8.5 accepted, and removes 136 functions from
the public corelib surface.
The list is below under Programs now refused.
Every refusal names the rule it enforces. Most of them are programs that were
already wrong: they failed later inside cc, leaked or double-freed a resource,
or read freed memory. Three are plain API changes: or_else is now a keyword,
the corelib helpers are private, and io.open_lines returns a Result.

macOS on Apple silicon

A darwin-arm64 archive is attached for the first time. Both compilers
build native Mach-O arm64 with no source change. The full make ci gate has
been green on macOS arm64 since 2026-09-19, and this archive is built twice and
compared byte for byte (make release-check), then extracted and checked
(make release-content).

Running the gate on macOS for the first time found defects that were not
specific to macOS. It was simply the first platform to show them:

  • Every Tycho server refused connections under a burst on macOS.
    net.listen asked for a backlog of 16. When the accept queue is full, Linux
    drops the SYN and the client retries, while macOS and the BSDs reset the
    connection. The backlog is SOMAXCONN now.
  • A server could hang on shutdown, about one run in eight, because a worker
    blocked in accept() never saw SIGTERM.
  • Floating point could round differently on ARM. The emitted C left FMA
    contraction to the C compiler. It fuses a*b + c on aarch64 and cannot on
    baseline x86-64, so the same program printed 0.30000000000000006 on one and
    ...004 on the other. Contraction is off now, so a program rounds as its
    source says on every host.

The binaries are not notarized by Apple. If macOS refuses to start them
after a browser download, clear the quarantine flag on the unpacked directory
with xattr -dr com.apple.quarantine tycho-v0.8.6-darwin-arm64.

New in the language

  • or_else: an early return that maps the error.

    cfg := load(path) or_else e: Err(ConfigErr(e))
    ok := parse(s) or_else _: false
    

    On Ok/Some the expression is the payload, as with or_return. On Err(e)
    the function returns the handler. The handler has the enclosing
    function's return type, which need not be a Result. It replaces the
    four-line match that unwraps a value or returns a wrapped error. It is
    refused in a function that returns nothing, because there is no value to
    return; use or_return or a match there. Spec §14.6.1. The tree's own code
    moved to it at 79 call sites.

  • size_of$(T) accepts an align(N) struct, so align can be observed
    from inside the language. The size is implementation-defined and is
    guaranteed to be a multiple of N.

  • math.min, math.max and math.clamp work lane by lane on a
    vector[N]T
    , as one compare and blend.

The layout and SIMD surface itself (vector[N]T, align(N), packed,
swizzling) shipped in 0.8.5. This release adds its first in-tree user,
tools/tycho-grade.

Programs now refused

  • or_else as an identifier. It is a keyword.
  • A call to a corelib internal helper. 136 functions such as
    json.parse_value and bignum.mag_cmp were public only because their names
    lacked a leading underscore. They are package-private now, and the error
    names the private spelling. Every function found being called from outside its
    package stayed public. The corelib surface is 423 functions, down from 559.
  • io.open_lines returning a nullable ptr. It returns
    Result(LineReader, IoErr). The null check becomes the Err arm.
    CHANGELOG.md shows the migration.
  • reserve or pop on a fixed-size [N]T or vector[N]T. The reference
    compiler emitted C that did not build. The shipped compiler accepted pop on
    a [4]int and silently shortened it.
  • Four misuses of a handle:
    • a use after a close(h) that closes it on every path;
    • close(h) on a handle parameter, which freed the resource twice;
    • an opener whose result is not bound directly to a variable, which leaked it
      for the life of the process;
    • a free: that does not name an extern fn taking that handle type.
  • An import one file declares and only a sibling file uses. It is reported
    as unused in the file that declares it.

Fixed

  • The shipped compiler returned freed memory in two shapes. An
    or_return/or_else payload returned straight to the caller was built in an
    arena the return had just freed. to_bytes(local) kept inside a returned
    value pointed into the freed scope; in core:httpd this corrupted a request
    body.
  • The shipped compiler crashed on x[a:b] over a struct or enum, with an
    internal message and no line. It is a type error now.
  • A local bounded[N]T grew past its capacity. It now traps on the push
    that overflows it.
  • core:tls did not guard an interior NUL in the host name. A string like
    "good.example.com\0anything" connected to, announced and verified
    good.example.com, while the caller's own checks saw the whole string. It
    fails closed now.
  • core:os refuses to spawn a .bat or .cmd on Windows. cmd.exe
    re-parses the command line and defeats the argument quoting. A PATH-resolved
    name with no extension is not caught.
  • Subnormal float literals became 0.0 on aarch64 Windows, through mingw's
    printf.
  • The vector-width lint gave x86-64 advice on ARM, a flag cc then refused.
    It now suggests a narrower vector first.
  • A generic instantiation error names the whole chain of calls, starting
    from the outermost one, which is the line you own.
  • Every build importing core:strings drew two -Wunused-value warnings from
    the reference compiler.
  • The build failed to link on Fedora with gcc 16: -static-pie needs -fPIE
    beside it.

Install

Download the tarball for your platform below, verify it, and unpack it:

tar xzf tycho-<version>-<os>-<arch>.tar.gz
cd tycho-<version>-<os>-<arch>
./tychoc examples/hello.ty && ./examples/hello

tychoc writes the binary beside its source unless you pass -o, so the
program built from examples/hello.ty is examples/hello.

The core library ships inside the tarball, beside the compiler, so there is
nothing to configure. You still need a C compiler (cc) on your PATH, because
Tycho transpiles to C: gcc, or clang 15 or newer. On macOS that is the Xcode
command line tools. Each tarball's SHA-256 is published alongside it.

What this release does not have

There has been no third-party security review, and the FFI boundary is
unsafe by design. See SECURITY.md, and
docs/internals/audit-brief.md if you are
willing to be one.

Two things are unverified rather than known, because you may hit them:

  • This Windows archive has been run under Wine, not on Windows. Its
    content gate passed under Wine 10 on x86-64 Linux: every .exe starts, and a
    real program is emitted, linked and run from a foreign directory. The
    toolchain itself has passed its suite on real Windows guests, 289/289 on both
    windows-x86_64 and windows-arm64 (make platform-check), but that was an
    earlier build, not this tarball.
  • Server shutdown on Windows has never been observed on Windows. The code
    reads sound: a worker re-reads the shutdown flag at least ten times a second,
    and the console handler shuts down the listener and every registered
    connection. What is untested is whether WSAPoll returns early on a socket
    that has been shut down, and whether a blocked accept is released. Expect
    wind-down within the 100 ms poll tick. Treat anything faster as unproven.

Status

0.8.6 is pre-1.0 and there are no stability guarantees: anything here may
change. ROADMAP.md lists what 1.0 requires, and
the blocking item is not engineering. Nobody outside this repo has written a
real program in Tycho yet. If you write one, the friction you hit is the most
useful thing you can send back.

v0.8.5: docs: name the host-compiler floor, and the three fixes, for 0.8.5

Choose a tag to compare

@StefanVonRanda StefanVonRanda released this 07 Sep 15:09

Tycho 0.8.5 — pre-1.0, no stability guarantees (see the README for
what that means in practice). Prebuilt binaries are attached, so you can try the
language without building from source.

This release refuses programs 0.8.0 accepted. Not a change of intent — those
programs were always invalid, and the shipped compiler failed to say so. A
survey of the C bootstrap found 495 user-facing rules, 262 of which no test in
the tree reached; the self-hosted compiler was missing a share of the rules it
had never been asked about. Around 270 rejection fixtures were written and the
gaps behind them closed. If your program stops compiling, the diagnostic names
the rule it broke. The two compilers now agree on every accept/reject verdict
across the whole corpus, and on which rule fired; the exact message text still
differs on 14 of 593 rejection fixtures.

Measured, not assumed: the 0.8.0 compiler was rebuilt and run beside this one
over the whole accept corpus — 420 programs, all 420 of which 0.8.0 compiled,
and all 420 still compile. Outside that corpus, three patterns 0.8.0 accepted
are now refused, and 0.8.0 did not merely accept them — it built and ran them:
push on a fixed-size array wrote past its storage, a slice of one read
storage that has no backing pointer, and an out-of-range u32 literal was
truncated to 0 in silence. A constant division overflow crashed the 0.8.0
compiler outright; it is a diagnostic now.

Windows finally works

Every executable in the 0.8.0 Windows archive fails to start. All four were
linked -pthread without -static and import a libwinpthread-1.dll the
archive does not carry. That archive is unusable and always was; this release is
the fix. The Windows tarball also now contains the self-hosted compiler
rather than the C bootstrap, which it shipped through 0.8.0 — worth about 3x
on a tree-walking benchmark. And tychoc.exe can be run from any directory: the
lookup for its own corelib split paths on / only, so with tycho on PATH
every build outside the archive directory failed.

The archive's contents are now gated, not just its bytes: it is extracted and
read on every check — the right compiler, the runtime present, no missing DLL
imports, every .exe starting, and a real program compiled and run from a
foreign directory.

All of that ran under Wine on Linux, not on Windows. No binary in this
release has been started on a real Windows kernel by anyone here.

Install

Download the tarball for your platform below, verify it, and unpack it:

tar xzf tycho-<version>-<os>-<arch>.tar.gz
cd tycho-<version>-<os>-<arch>
./tychoc examples/hello.ty && ./examples/hello

tychoc writes the binary beside its source unless you pass -o, so the
program built from examples/hello.ty is examples/hello.

The core library ships inside the tarball, beside the compiler, so there is
nothing to configure. You still need a C compiler (cc) on your PATH — Tycho
transpiles to C. Each tarball's SHA-256 is published alongside it.

New in the language

  • vector[N]T — a fixed array whose arithmetic is one machine instruction.
    A power-of-two count from 2 to 64, int/float/f32 elements. In 0.8.0 this
    type existed in the documentation and not in the compiler that shipped: the
    self-hosted compiler lowered it to a dynamic array and emitted no vector
    instructions at all. It emits real ones now.
  • --target <level> — raise the x86-64 baseline: x86-64-v2, -v3, -v4.
    Worth knowing, because plain x86-64 is SSE2 and a register is 16 bytes, so a
    32-byte vector[4]float is split in half in every operation and can be slower
    than the plain array it replaced. The compiler warns when that happens and
    says which flag fixes it. Without the flag nothing changes: an unflagged build
    is byte-identical to one made before the flag existed.
  • align(N) and packed — state a struct's layout. packed is byte-exact
    with no padding; align(N) raises alignment, capped at 8 because that is what
    the arena guarantees. A request the allocator cannot honour is refused at
    compile time rather than rounded down in silence.
  • Simultaneous assignment — (x, y) = (y, x). Targets are places, so a
    field, an array element or a map value may stand on the left, and every
    right-hand side is evaluated before any target is written. (a, b) = (b, a+b)
    is one Fibonacci step.
  • Swizzling — v.(x, y) is a tuple of two components of one value, and it
    reads, binds and assigns. Fixed arrays and vectors of four lanes or fewer name
    them .x .y .z .w, or .r .g .b .a for the same four, wherever a field
    access is accepted.
  • A byte bridge — to_bytes, from_bytes$(T) and size_of$(T) over a
    packed struct, little-endian on every host.

Fixed

  • The example server starved under load. A worker served one connection
    start to finish, so 64 idle peers delayed a request by 2080 ms, and 64 parked
    keep-alive peers meant only four requests — the worker count — could be
    answered at all. Rewritten around one poll(2) per worker: 21–33 ms with all
    64 answered, unchanged at 256.
  • A double free of a core:crypto, core:tls, core:http or core:image
    handle
    segfaulted, and using one after free returned a garbage number and
    exited 0. Both die by name now.
  • Float-to-integer conversions were undefined for a NaN, an infinity or an
    out-of-range value. They are checked at run time.
  • The format parsers stopped failing open — core:csv, core:json,
    core:toml, core:cli and core:markdown.
  • make failed on a fresh clone under Debian and Ubuntu. The runtime is
    escaped into a C string literal by an awk rule, and mawk — the default awk
    on both — halves a backslash where every other awk doubles it, so all 89
    backslashes in the runtime reached cc unescaped and the build died at
    \x used with no following hex digits. The rule no longer depends on which
    awk is installed. Present since 0.5.0.
  • vector[N]T and swizzling did not compile under any clang. The lowering
    took the address of a vector lane, which gcc permits and clang refuses in
    every version. Five fixtures failed; the whole corpus now agrees between gcc
    and clang 19. clang 15 or newer — clang 14 miscompiles the emitted C and
    is not supported.
  • --print-deps reported nothing for core:sqlite, so make corelib
    failed rather than skipped where libsqlite3 is absent. A package's deps file
    is now read whether or not it also ships a C shim.

Security

  • core:http refuses file:// URLs — a URL from anywhere untrusted could read
    a local file through the HTTP client.
  • tycho-httpd refuses ambiguous request framing (request smuggling), and the
    example server refuses a symlink escape out of its document root.
  • Three sites interpolated attacker-shaped text into a shell command line; all
    are quoted now.
  • tycho-rsa is gone. A pure-Tycho RSA cannot be constant-time — the
    modular exponentiation leaks the key through timing — and padding it did not
    change that. Use core:crypto, which is OpenSSL.

The surface moved, deliberately

0.8.0 froze the keyword set, the builtin set and every corelib signature, and
said no new language features before 1.0. This release breaks that: the layout and
SIMD work above needed surface, and it was judged worth taking now rather than
after 1.0, when it could not be taken at all. The lock still exists and still
gates — 115 keywords, 41 builtins, 559 corelib functions — but it records what
was added instead of forbidding additions. Corelib may still gain a function and
may not lose one or change a signature.

What this release does not have

There has been no third-party security review, and the FFI boundary is
unsafe by design — see SECURITY.md, and
docs/internals/audit-brief.md if you are
willing to be one.

One thing is unverified rather than known, because you may hit it:

  • Server shutdown on Windows has never been observed on Windows. The code
    reads sound in both halves: a worker parks in net.wait_readable with a tick
    of at most 100 ms (server/main.ty@POLL_TICK_MS), so it re-reads the
    shutdown flag ten times a second whether or not anything wakes it, and the
    console handler shuts down the listener and every registered connection
    (corelib/signal/signal_shim.c@sigx_ctrl_handler). What is untested is
    whether WSAPoll returns early on a socket that has been shut down, and
    whether a blocked accept is released; both are Windows-version dependent
    and no lane here can reach a real Windows kernel. Expect wind-down within the
    poll tick; treat anything faster as unproven.

Status

0.8.5 is pre-1.0 and there are no stability guarantees: anything here may
change. ROADMAP.md lists what 1.0 requires, and
the blocking item is not engineering — it is that nobody outside this repo has
written a real program in Tycho yet. If you write one, the friction you hit is
the most useful thing you can send back.

Tycho 0.8.0

Tycho 0.8.0 Pre-release
Pre-release

Choose a tag to compare

@StefanVonRanda StefanVonRanda released this 02 Sep 09:20

Tycho 0.8 — pre-1.0, no stability guarantees (see the README for
what that means in practice). Prebuilt binaries are attached, so you can try the
language without building from source.

This release does not break source compatibility with 0.7. That is measured,
not assumed: the 0.7.0 compiler was rebuilt and run beside this one over the
whole accept corpus — 300 programs, 297 of which 0.7.0 compiled, all 297 still
compile, and three that 0.7.0 refused now work.

The compiler is now written in Tycho

tychoc in the Linux tarball is tychoc1, the self-hosted compiler. It is built
in two stages: the C bootstrap in src/tychoc.c builds stage 1, and stage 1
builds the compiler you get, so it carries its own optimisations. The bootstrap
is not going away — it is how a clean checkout gets started.

On Windows the tarball still contains the C bootstrap. The mingw64 build
cross-compiles src/tychoc.c and does not cross-build the self-hosted compiler.
Same language, same version, different implementation.

On its own source the self-hosted compiler now compiles faster than the
bootstrap — 73 ms against 105 ms, minimum of ten runs — and is level on the other
inputs measured. It started this cycle five times slower.

Install

Download the tarball for your platform below, verify it, and unpack it:

tar xzf tycho-<version>-<os>-<arch>.tar.gz
cd tycho-<version>-<os>-<arch>
./tychoc examples/hello.ty && ./examples/hello

tychoc writes the binary beside its source unless you pass -o, so the
program built from examples/hello.ty is examples/hello.

The core library ships inside the tarball, beside the compiler, so there is
nothing to configure. You still need a C compiler (cc) on your PATH — Tycho
transpiles to C. Each tarball's SHA-256 is published alongside it.

What's new

  • Three generic shapes that 0.7 refused now compile: a generic function
    returning an array under a where clause, a generic constructor taking a bare
    [], and a lambda argument at a generic call site.
  • io.make_dir_all — mkdir -p, which tycho-ar had hand-rolled — plus
    io.append_text, io.copy and strings.format_g17.
  • Array-literal codegen was quadratic. 30,000 elements went from over a
    minute to 20 ms.
  • Three new tools: tycho-diff, tycho-fold, tycho-hash.
  • Diagnostics: several messages named the compiler's internals rather than
    the mistake, and two offered confident suggestions built on a wrong
    assumption. Those are rewritten.

Deprecated

  • decimal.from_str fails open — it returned 0.15 for "1.5x" — and is
    deprecated. Use decimal.parse for a checked read, or
    decimal.parse_unchecked if you really want the old behaviour. It still
    works in 0.8 and warns.

The language surface is frozen

Since 2026-08-22 the keyword set, the builtin set and every corelib signature are
locked and gated: 101 keywords, 41 builtins, no additions or removals. Corelib
may gain a function; it may not lose one or change a signature. No new
language features before 1.0.
The point is that the surface stops moving long
enough to be learned and depended on.

What this release does not have

There has been no third-party security review, and the FFI boundary is
unsafe by design — see SECURITY.md, and
docs/internals/audit-brief.md if you are
willing to be one.

One limit is stated rather than fixed, because you may hit it:

  • On Windows, a server winds down within its idle timeout rather than
    within a millisecond: a thread parked in recv is not released by the
    shutdown handler as it is on Linux. Nothing is lost or corrupted.

Status

0.8 is pre-1.0 and there are no stability guarantees: anything here may
change. ROADMAP.md lists what 1.0 requires, and
the blocking item is not engineering — it is that nobody outside this repo has
written a real program in Tycho yet. If you write one, the friction you hit is
the most useful thing you can send back.

Tycho 0.7.0

Tycho 0.7.0 Pre-release
Pre-release

Choose a tag to compare

@StefanVonRanda StefanVonRanda released this 15 Aug 12:50

Tycho 0.7 — pre-1.0, no stability guarantees (see the README for
what that means in practice). Prebuilt binaries are attached, so you can try the
language without building from source.

This release breaks source compatibility with 0.6. Several shapes that
compiled before are now refused, and two of them were live memory errors rather
than style: a copied handle gave one pointer two owners and glibc reported
double free detected in tcache 2 from a four-line program, and a copied channel
aliased one channel to two owners so a send through one was received through the
other. Also refused now: sink/inout on an affine type, a bare handle as a
struct field, and a bounded[N]T generic field that had silently degraded to a
plain fixed array. JsonErr gains a variant, which breaks an exhaustive match
on it. Every migration is mechanical; CHANGELOG.md's breaking
sections state each as "what you wrote" → "what you write" and are the list, not
this paragraph.

Security fixes

core:crypto — two of these are credential-grade, and both failed by
collision rather than by weakness, which is the mode that does not look like
a bug:

  • A password was truncated at its first NUL. The shim called strlen, so
    "secret\0A" and "secret\0B" derived the same key — and the same one as
    "secret". Two distinct credentials authenticated against each other. The
    length is passed explicitly now.
  • ct_equal compared two hex strings by their prefixes, for the same reason.
    Not reachable in the MAC-verification shape — a computed MAC carries no NUL, so
    the lengths disagreed and the answer was already false — but a caller comparing
    two supplied values had a collision.
  • The plaintext was left in freed memory. aead_encrypt released the
    plaintext it decoded and aead_decrypt the one it recovered, neither wiped.
    The recycled hex return buffer also kept its last value — including a key from
    key_export_hex — for the life of the thread.
  • Key import was not constant-time. The hex decode rejected at the first bad
    digit, which times the offset of the error. Branch-free now, verified under
    valgrind rather than by timing: 7 secret-dependent branches before, 0 after.

Elsewhere:

  • core:http can be pointed at a private CA (SSL_CERT_FILE /
    SSL_CERT_DIR), which is what makes its certificate verification testable at
    all. It was correct before and unprovable; make http-verify now holds it.
  • markdown.render no longer emits javascript: or data: hrefs, and
    escapes attribute values as well as text.
  • json.parse_checked refuses a document nested past 2000 instead of
    aborting the process. A deeply nested document used to kill it with
    tycho: stack overflow, so the one entry point with an error channel had a
    hole exactly where an attacker controls the input.
  • sqlite bound parameters survive an interior NUL, and toml.parse refuses
    a repeated [table] header that used to delete the first one.

There is still no third-party audit of any of this — see
SECURITY.md, and
docs/internals/audit-brief.md if you are willing
to be one.

Install

Download the tarball for your platform below, verify it, and unpack it:

tar xzf tycho-<version>-<os>-<arch>.tar.gz
cd tycho-<version>-<os>-<arch>
./tychoc examples/hello.ty && ./examples/hello

tychoc writes the binary beside its source unless you pass -o, so the
program built from examples/hello.ty is examples/hello.

The core library ships inside the tarball, beside the compiler, so there's nothing to
configure. You still need a C compiler (cc) on your PATH — Tycho transpiles to C. Each
tarball's SHA-256 is published alongside it.

Breaking changes

Full detail and migrations in CHANGELOG.md; the short form:

  • A binding may not start with an uppercase letter. Locals, parameters,
    loop variables and pattern bindings must start lowercase or _. The
    uppercase namespace belongs to types, enum variants and consts, so a binding
    can no longer shadow a constructor. const is exempt. Rename the binding.
  • is is now a reserved keyword (the variant test, below). A variable or
    function named is no longer compiles.
  • A function may not be named Ok, Err, Some, None, or after any
    enum variant or const. Rename it.
  • core:iter predicates return bool, not int — filter, try_filter,
    count, any. Write fn(x: int) -> bool: x % 2 != 0 where you wrote
    fn(x: int) -> int: x % 2.
  • image.decode/encode and compress.decompress return a Result
    instead of an empty-or-sentinel value, so a corrupt input is no longer
    indistinguishable from a legitimately empty one. Add or_return or a match.
  • tycho-ar x exits 1 when an mtime could not be restored. It now finishes
    the extraction and warns per member rather than dying part-way; exit 0 still
    means fully restored.

What's new

  • is, the variant test — x is VariantName for an enum, an Option or a
    Result, so you can ask without destructuring: if r is Ok:, o is Some.
  • A [string] may cross the FFI as an extern parameter, arriving in C as
    (const char *const *, long). This retired core:os's whole argv-builder
    protocol; os.exec and os.exec_out are unchanged for callers.
  • io.mtime / io.set_mtime, strings.slice_bytes / slice_str (a
    fail-closed slice, where the built-in one clamps), iter.try_map /
    try_filter
    (short-circuit on the first Err), result.map_err_with
    (translate an error and keep the cause), and decimal.div with rounding
    modes and three named failure causes.
  • f-string interpolations evaluate left to right. They printed in source
    order but called in reverse, because the holes became arguments to one C
    function and C leaves argument order unspecified. Observable whenever a hole
    carries a call with a side effect.
  • A compiler use-after-free is fixed. A stale pointer into the signature
    table, dangling after a generic instantiation reallocated it, produced bogus
    refusals of valid programs that came and went with unrelated edits.
  • Better diagnostics: a refusal inside a generic now names the call site
    that instantiated it, and a non-exhaustive Result/Option match names the
    uncovered variant instead of claiming a side was missing.

Two limits remain stated rather than fixed, because you will hit them:

  • core:net has no readiness polling. No poll/select/epoll/
    O_NONBLOCK anywhere in the package, so a server's worker count is a hard
    ceiling on concurrent connections and one slow client occupies one worker.
  • On Windows, a server winds down within its idle timeout rather than
    within a millisecond: a thread parked in recv is not released by the
    shutdown handler as it is on Linux. Nothing is lost or corrupted.

What this release does not have

Stated plainly, because the previous draft of these notes advertised the
opposite. Until 2026-07-29 every program was compiled by two independent
implementations — tychoc and the self-hosted tychoc0 — which had to agree.
That differential is retired and nothing replaces it. It caught real
defects, specifically changes that silently narrowed what the language accepts,
and that class of defect now has no second opinion. compiler/tychoc0.ty
remains in the tree as the artifact proving Tycho self-hosts, and as the
compiler's hardest sanitizer input, but it is no longer a check.

There has also been no third-party security review, and the FFI boundary is
unsafe by design — see SECURITY.md.

Status

0.7 is pre-1.0 and there are no stability guarantees: anything here may
change — this release broke source compatibility in six places and the next one
may too. ROADMAP.md lists what 1.0 requires, and
the blocking item is not engineering — it is that nobody outside this repo has
written a real program in Tycho yet. If you write one, the friction you hit is
the most useful thing you can send back.