0.9.0 Release Notes

Zero the Ziguana

Download & Documentation

Zig is a general-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.

Backed by the Zig Software Foundation, the project is financially sustainable. These core team members work on Zig full-time:

Please consider a recurring donation to the ZSF to help us pay more contributors!

This release features 6 months of work: changes from 177 different contributors, spread among 2023 commits.

One sentence summary: The Toolchain "just works" in more cases, many bugs were fixed, the Self-Hosted Compiler is 44% complete, the Support Table is expanded, there were a handful of Language Changes, the project started Performance Tracking, and the Standard Library, although unstable, became more useful.

Table of Contents §

Isaac Freund Joins the Core Zig Team §

Ziggy the Ziguana

To many, the reaction to this announcement will be something like, "Oh? I thought he was already on the core zig team." This goes to show how obvious this decision was.

Isaac is a level-headed, talented, careful, patient, and diligent programmer. You might be viewing these release notes through his dynamic tiling Wayland compositor, River.

He also works on TigerBeetle - the world's fastest financial accounting database.

In addition to lending his technical expertise to the Zig project, Isaac has proven to be a steadfast community leader, setting an example for how to treat others with kindness and respect.

Please give him a warm welcome to the core Zig team.

Support Table §

Tier System §

A green check mark (✅) indicates the target meets all the requirements for the support tier. The other icons indicate what is preventing the target from reaching the support tier. In other words, the icons are to-do items. If you find any wrong data here please submit a pull request!

Tier 1 Support §

freestanding Linux 3.16+ macOS 10.13+ Windows 8.1+ WASI
x86_64 N/A
x86 #1929 🐛📦 💀 #537 🐛📦 N/A
aarch64 #2443 🐛 🐛📦🧪 N/A
arm #3174 🐛📦 💀 🐛📦🧪 N/A
mips #3345 🐛📦 N/A N/A N/A
riscv64 #4456 🐛📦 N/A N/A N/A
sparcv9 #4931 🐛📦🧪 N/A N/A N/A
wasm32 N/A N/A N/A

Tier 2 Support §

free standing Linux 3.16+ macOS 10.13+ Windows 8.1+ FreeBSD 12.0+ NetBSD 8.0+ DragonFlyBSD 5.8+ UEFI
x86_64 Tier 1 Tier 1 Tier 1 Tier 1
x86 Tier 1 💀 🔍 🔍 N/A
aarch64 Tier 1 Tier 1 🔍 🔍 🔍 N/A 🔍
arm Tier 1 💀 🔍 🔍 🔍 N/A 🔍
mips64 N/A N/A 🔍 🔍 N/A N/A
mips Tier 1 N/A N/A 🔍 🔍 N/A N/A
powerpc64 📖 💀 N/A 🔍 🔍 N/A N/A
powerpc 💀 N/A 🔍 🔍 N/A N/A
riscv64 Tier 1 N/A N/A 🔍 🔍 N/A 🔍
sparcv9 Tier 1 N/A N/A 🔍 🔍 N/A N/A

Tier 3 Support §

freestanding Linux 3.16+ Windows 8.1+ FreeBSD 12.0+ NetBSD 8.0+ UEFI
x86_64 Tier 1 Tier 1 Tier 1 Tier 2 Tier 2 Tier 2
x86 Tier 1 Tier 2 Tier 2 Tier 2
aarch64 Tier 1 Tier 2
arm Tier 1 Tier 2
mips64 Tier 2 Tier 2 N/A N/A
mips Tier 1 Tier 2 N/A N/A
riscv64 Tier 1 Tier 2 N/A
powerpc32 Tier 2 Tier 2 N/A N/A
powerpc64 Tier 2 N/A N/A
bpf N/A N/A
hexagon N/A N/A
amdgcn N/A N/A
sparc N/A N/A
s390x N/A N/A
lanai N/A N/A
csky N/A N/A
freestanding emscripten
wasm32 Tier 1

Tier 4 Support §

Tier 4 targets:

WebAssembly §

Luuk de Gram writes:

During this release we hit some major milestones. The Self-Hosted Linker for wasm was rewritten, with knowledge gained from building a stand-alone wasm linker. The linker is now capable of building a memory layout with a virtual stack, as well as perform relocations. Those are some of the neccesary features required to be able to implement zig test. As of today, the self-hosted wasm backend is now capable of passing 13% of all the behavioral tests. With all of those changes, contributing to the wasm backend is made a lot easier. The linker is now capable enough where only knowledge about wasm is required to contribute to the backend.

Those features were implemented in the following Pull Requests:

The following 4 new linker flags were added for WebAssembly (#8633):

With these items addressed, Zig is one of the supported languages for the WASM-4 Fantasy Console. Tom, the creator of Context Free, is hosting a game jam on January 14 - 23.

Additionally, the linker frontend for wasm targets now supports the following flags:

Contributors: Luuk de Gram, Takeshi Yoneda, Jakub Konka, Andrew Kelley

WASI §

WebAssembly System Interface is a pseudo-operating system that provides sandboxed I/O and other system capabilities to WebAssembly.

32-bit x86 §

Zig now has Continuous Integration testing enabled for Linux builds against glibc.

RISC-V §

Contributors: Andrew Kelley, Michael Dusan, vole-dev

NetBSD §

Windows §

Contributors: Andrew Kelley, viri, mchudleigh, Jonathan Marler, Travis Martin, LemonBoy

ARM64 Windows §

0.9.0 is the first release which publishes a build of Zig for ARM64 Windows (#9102): zig-windows-aarch64-0.9.0.zip

Be warned that it has not been properly tested yet; Zig developers are still in the process of acquiring the hardware needed to run ARM64 Windows. Thanks to Martin Storsjö for the tip about the ECS LIVA Mini Box QC710.

UEFI §

Contributors: Stephen von Takach, Nameless, Sreehari Sreedev

iOS §

Thanks to major improvements in the Self-Hosted Linker combined with support for compiling Objective-C, along with miscellaneous improvements in the Self-Hosted Compiler, Zig now targets iOS and iPhone Simulator platforms. See kubkon/zig-ios-example for a complete example.

(#9532)

Solaris/Illumos §

Initial bringup of the Solaris/Illumos port by Stephen Gregoratto.

Haiku §

Improved Haiku support in the Standard Library by Al Hoang (#10073).

Thanks to waddlesplash for stopping by and reviewing the patch.

macOS §

Generally, Zig 0.9.0 works much better than 0.8.x on macOS, due to major improvements in the Self-Hosted Linker, Versioned C Headers, and miscellaneous improvements, such as:

Versioned C Headers §

The fetch-them-macos-headers project has been improved to account for headers from multiple versions of macOS. Currently, these versions are 10 (Catalina), 11 (Big Sur), and 12 (Monterey). As Apple releases new major versions of macOS, this project will track the latest three, which matches the security update policy of Apple.

The tool has been improved to detect which files are different across both versions and architectures, dividing the files up into eight categories:

Files that are common between multiple categories are put into the "any" ones, as least specific as possible, to save on installation size.

Long story short, this fixes bugs having to do with incompatible header files, and comes at a minimal cost of additional installation size:

Before these changes:

After these changes:

Linux §

Contributors: Andrew Kelley, LemonBoy, Jens Goldberg, Vincent Rischmann, Aydin Mercan, Kenta Iwasaki, Hiroaki Nakamura, Felix "xq" Queißner

io_uring §

OpenBSD §

Contributors: Sébastien Marie, Dante Catalfamo

SPARC §

Contributors: Koakuma

Plan 9 §

A new linker backend for the Plan9 a.out executable format has been written for the Self-Hosted Compiler by Jacob G-W. The linker only works with the x86_64 and aarch64 backends because LLVM cannot emit Plan9 object files. Experimental source location debug info is emitted from this linker.

It can be used with -target x86_64-plan9.

Hopefully Zig can soon join Go as the second modern language that is a first class citizen on Plan9!

Custom Operating Systems §

People have started to use Zig to build their own operating systems due to the standard library being available and unopinionated.

Some improvements to this use case in 0.9.0:

Contributors: N00byEdge, mason1920, rgreenblatt

Documentation §

Ziggy the Ziguana

Contributors: Andrew Kelley, pfg, Mr. Paul, Daniele Cocca, Lee Cannon, Travis Staloch, Jacob G-W, Robin Voetter, Auguste Rame, Meghan, Michael Byrne, David May, Dmitry Matveyev, Evan Haas, Exonorid, Isaac Freund, Jarred Sumner, Jonathan Marler, Josh Soref, Nathan Michaels, Norberto Martínez, Nulo, Paul, Philipp Lühmann, Roman Frołow, Rory O’Kane, Veikka Tuominen, bnprks, kprotty, travisstaloch, yetanothercheer

Language Changes §

@byteOffsetOf is renamed to @offsetOf. zig fmt automatically performs this upgrade for you.

Address Spaces §

During this release, a language proposal for pointer address spaces was materialized and partially implemented in the Self-Hosted Compiler. This gives the user the ability to attribute a pointer or variable with a certain address space, after which the compiler will be able to generate specialized instructions to load or store variables to those different address spaces. While this feature is relatively niche, it is an invaluable feature for embedded hardware and graphics processing units, and so broadens the number of targets that Zig can reasonably to support.

To instruct the linker to place a variable within a particular address space (if supported), or to create a pointer pointing to a value in a particular address space, one can use the addrspace keyword.

pub const will_be_placed_in_flash: i32 addrspace(.flash) = 123;

pub fn readFlash(ptr: *addrspace(.flash) i32) i32 {
    return ptr.*;
}

Thanks to Robin Voetter for the proposal, specification, and implementation of these changes.

More Builtins Return String Literals §

@tagName, @errorName, @typeName, and @embedFile now have a return type of *[N:0]const u8 instead of []const u8. This means that these builtins can be used anywhere a string literal would be used. In other words, they can be coerced into any of these types:

Similarly, the file and fn_name fields of std.builtin.SourceLocation returned by @src now are [:0]const u8, making it possible to pass them as null-terminated strings without copying them.

@minimum and @maximum §

New builtins: @minimum and @maximum

These builtins accept integers, floats, and vectors of either. In the latter case, the operation is performed element wise.

NaNs are handled as follows: if one of the operands of a (pairwise) operation is NaN, the other operand is returned. If both operands are NaN, NaN is returned.

See also: SIMD

@select §

New builtin: @select

@select(comptime T: type, pred: @Vector(len, bool), a: @Vector(len, T), b: @Vector(len, T)) @Vector(len, T)

Selects values element-wise from a or b based on pred. If pred[i] is true, the corresponding element in the result will be a[i] and otherwise b[i].

See also: SIMD

SIMD §

In this release, the following builtins have been improved to support SIMD vectors:

These SIMD-enabled builtins are new in 0.9.0:

SIMD support in Zig pretty far along, with more improvements still planned. If you try to use SIMD in your Zig codebase and you find that it does not satisfy your use case, we want to hear from you! Please open an issue and describe the situation.

SIMD Vector Syntax is planned so that one is not required to use @Vector.

Documentation in the language reference is bare bones and needs to be fleshed out.

There is still some missing functionality which you can see listed in #903.

The focus now has shifted to implementing SIMD in the Self-Hosted Compiler since that is ultimately what everyone will be using.

@intToEnum Implicitly Performs @intCast §

This is a backwards-compatible language change.

Previously, @intToEnum coerced its integer operand to the integer tag type of the destination enum type, often requiring the callsite to additionally wrap the operand in an @intCast. Now, the @intCast is implicit, and any integer operand can be passed to @intToEnum.

The same as before, it is illegal behavior to pass any integer which does not have a corresponding enum tag.

In summary, you can change this:

return @intToEnum(Tag, @intCast(@typeInfo(Tag).Enum.tag_type, number));

...into this:

return @intToEnum(Tag, number);

Shadowing Declarations §

Locals are never allowed to shadow declarations, but declarations will sometimes need to have the same name as each other. Consider this case:

pub const List = struct {
    head: *Node,

    pub const Node = struct {
        bar: i32,
        next: *Node,

        pub fn init() Node {
            // ...
        }
    };

    pub fn init() List {
        // ...
    }
};

Consider the full namespace paths of the functions:

This is completely reasonable and expected, but it causes, within the scope of Node, for the init identifier to be ambiguous. For example, if we reference the init function from the inner scope, we get an error:

test.zig
pub const List = struct {
    head: *Node,

    pub const Node = struct {
        bar: i32,
        next: *Node,

        pub fn init() Node {
            // ...
        }

        test "example" {
            _ = init;
        }
    };

    pub fn init() List {
        // ...
    }
};
Shell
$ zig test test.zig
docgen_tmp/test.zig:13:17: error: ambiguous reference
            _ = init;
                ^
docgen_tmp/test.zig:8:13: note: declared here
        pub fn init() Node {
            ^
docgen_tmp/test.zig:17:9: note: also declared here
    pub fn init() List {
        ^

This is resolved by eliminating the ambiguity by accessing the function via its namespace:

pub const List = struct {
    head: *Node,

    pub const Node = struct {
        bar: i32,
        next: *Node,

        pub fn init() Node {
            // ...
        }

        test "example" {
            _ = Node.init;
            _ = List.init;
        }
    };

    pub fn init() List {
        // ...
    }
};

No error this time.

Similarly, variables are now allowed to have the same names as primitives such as i32 and null, however they must use the @"" sytax in order to disambiguate (#6062).

The motivation for these language decisions is to reduce the amount of non-local awareness a person reading Zig code must have in order to confidently make changes.

Keywords Deleted: true, false, undefined, null §

Now these are primitives provided by the language, same as void, u32, etc.

Consequently one can now use these tokens as, for example, struct field names:

const Foo = struct {
    true: i32,
    false: i32,
    undefined: i32,
    null: i32,
};

However it is still required to use @"" syntax to disambiguate between references to primitives or same-named identifiers. Example:

test.zig
test "example" {
    var i32: bool = true;
}
Shell
$ zig test test.zig
docgen_tmp/test.zig:2:9: error: name shadows primitive 'i32'
    var i32: bool = true;
        ^
docgen_tmp/test.zig:2:9: note: consider using @"i32" to disambiguate
    var i32: bool = true;
        ^

usingnamespace No Longer Affects Identifier Lookup §

This is a breaking change that is likely to affect most codebases.

usingnamespace no longer imports identifiers into the current namespace:

undeclared.zig
const std = @import("std");

usingnamespace std.debug;

pub fn main() !void {
    try print("hello", .{});
}
Shell
$ zig build-exe undeclared.zig
docgen_tmp/undeclared.zig:6:9: error: use of undeclared identifier 'print'
    try print("hello", .{});
        ^

It can still be used, however, to add declarations to a namespace. For example, a file called c.zig which contains the following:

pub usingnamespace @cImport({
    @cInclude("stdio.h");
    @cInclude("math.h");
    @cInclude("time.h");
    @cInclude("epoxy/gl.h");
    @cInclude("GLFW/glfw3.h");
    @cDefine("STBI_ONLY_PNG", "");
    @cDefine("STBI_NO_STDIO", "");
    @cInclude("stb_image.h");
});

This was the intended use case for usingnamespace all along, and it still works the same.

The motivations for this change are:

Compilation speed is a serious goal of the project. Zig the language is willing to take on restrictions and design limitations in order to facilitate potential compilation speed enhancements.

There is an open proposal to rename the keyword.

c_void renamed to anyopaque §

c_void actually has nothing to do with C; it is just an opaque type that has some relaxed coercion rules surrounding type erasure.

In Zig 0.9.0 it is renamed to anyopaque but it has the exact same semantics.

zig fmt automatically performs the rename.

Saturating Arithmetic §

Zig now has syntax to perform arithmetic that does not wrap, overflow, or invoke illegal behavior. Instead, if the result does not fit into the destination type, the value is clamped to the minimum or maximum:

saturating_arithmetic.zig
const std = @import("std");
const expect = std.testing.expect;

test "example" {
    var x: u8 = 200;
    var y: u8 = 100;
    try expect(x +| y == 255);
    try expect(y -| x == 0);
    try expect(y *| x == 255);
    try expect(y <<| x == 255);
}
Shell
$ zig test saturating_arithmetic.zig
1/1 test "example"... OK
All 1 tests passed.

Relevant issues: #9949 #9679 #9619

Compile Errors for Unused Locals §

It is no longer possible to have a local variable or parameter that is not referenced:

test.zig
test "example" {
    var x: i32 = 1234;
}
Shell
$ zig test test.zig
docgen_tmp/test.zig:2:9: error: unused local variable
    var x: i32 = 1234;
        ^

This has been planned since before 0.1.0 was released, and it is planned to introduce more compile errors for unused things, despite this having been a controversial change. Zig will never have a "sloppy mode" flag.

The motivation for this change is helping with refactoring and reworking code, and preventing bugs, ultimately saving developer time.

If a local is intentionally unused, it can be discarded, like this:

unused.zig
test "example" {
    var x: i32 = 1234;
    _ = x;
}
Shell
$ zig test unused.zig
1/1 test "example"... OK
All 1 tests passed.
Zero the Ziguana

The eventual goal for the developer experience related to errors for unused things is that it will work similar to the "Organize Imports" button in Eclipse that Java developers are familiar with. Idea being that IDEs would have a simple keyboard shortcut that would automatically add discards for all unused things, eliminating this compile error as a stumbling block when a developer is experimenting, and yet ensuring that the source code on disk documents the code smell with explicit discarding of unused things.

Another possibility is that zig fmt could grow a --fix-unused flag, performing this hypothetical operation outlined above.

Please note there is an accepted proposal to add an unused keyword, making it possible to detect conflicts between things being marked unused and actually being used, and improving tooling integration.

@export with Field Access §

@export creates a symbol in the output object file.

Previously, declaration must be an identifier. Now, it could be field access expression:

Inline Assembly Requires String Literals §

The end-game for inline assembly is that the syntax is more integrated with Zig, and it will not allow string concatenation for the assembler code, for the same reasons that Zig does not have a preprocessor.

However, inline assembly in zig right now is lacking for a variety of use cases (take a look at the open issues having to do with inline assembly for example), and being able to use comptime expressions to concatenate text is a workaround that real-world users are exploiting to get by in the short term.

This release introduces "assembly code must use string literal syntax" as a compile error when using the Self-Hosted Compiler, but allows it through when using the Bootstrap Compiler.

@prefetch §

New builtin: @prefetch

@prefetch(ptr: anytype, comptime options: std.builtin.PrefetchOptions)

This builtin tells the compiler to emit a prefetch instruction if supported by the target CPU. If the target CPU does not support the requested prefetch instruction, this builtin is a noop. This function has no effect on the behavior of the program, only on the performance characteristics.

The ptr argument may be any pointer type and determines the memory address to prefetch. This function does not dereference the pointer; it is perfectly legal to pass a pointer to invalid memory to this function and no illegal behavior will result.

The options argument is the following struct:

builtin.zig
/// This data structure is used by the Zig language code generation and
/// therefore must be kept in sync with the compiler implementation.
pub const PrefetchOptions = struct {
  /// Whether the prefetch should prepare for a read or a write.
  rw: Rw = .read,
  /// 0 means no temporal locality. That is, the data can be immediately
  /// dropped from the cache after it is accessed.
  ///
  /// 3 means high temporal locality. That is, the data should be kept in
  /// the cache as it is likely to be accessed again soon.
  locality: u2 = 3,
  /// The cache that the prefetch should be preformed on.
  cache: Cache = .data,

  pub const Rw = enum {
      read,
      write,
  };

  pub const Cache = enum {
      instruction,
      data,
  };
};

Bootstrap Compiler §

In the previous release, as well as this release, the main Zig compiler everybody uses is the bootstrap compiler, written in C++, also known as "stage1". Despite the main focus of this release cycle being the Self-Hosted Compiler, there were some improvements to stage1 as well.

Contributors: Andrew Kelley, LemonBoy, Martin Wickham, Robin Voetter, Daniele Cocca, Takeshi Yoneda, Auguste Rame, Daniele Cocca, Jacob G-W, Lee Cannon, LemonBoy, Matthew Borkowski, Travis Staloch, leesongun, travisstaloch, Belhorma Bendebiche, Evan Haas, Exonorid, Frank Denis, Jakub Konka, Jay Petacat, Josh Soref, Kirk Scheibelhut, Meghan Denny, Michael Dusan, Richard Eklycke, Stephen Gregoratto, Veikka Tuominen, mlarouche, vole-dev, Žiga Željko

Better Awareness of Unwind Tables §

Main issue: #9046

Moved Unreachable Code Errors to Self-Hosted §

The Self-Hosted Compiler is so fast that Zig 0.9.0 additionally runs the first three phases of the self-hosted pipeline (tokenizing, Parser, and AST lowering), before invoking the bootstrap compiler, to enable Compile Errors for Unused Locals among other kinds of compile errors.

At first it seemed this would be a small perf hit to additionally run part of the self-hosted compiler in front of the bootstrap compiler, but it would be worth it to gain access to the compile errors that are only implemented in the self-hosted compiler.

But something delightful happened.

This change allowed us to move the "unreachable code" compile error from the bootstrap compiler to the self-hosted compiler. This made it possible to delete a couple of fields from some structs within the bootstrap compiler, resulting in a small performance improvement. But because the self-hosted compiler is so fast, the performance gain outweighed the performance cost!

So we actually improved performance by additionally running the self-hosted compiler in front of the bootstrap compiler.

Self-Hosted Compiler §

The main focus of this release cycle was the self-hosted compiler (also known as "stage2").

Here is an infographic to communicate a sense of progress:

Progress Report Progress Report

Currently we have 467 out of 1072 behavior tests passing with the LLVM Backend. Once this reaches 100% we can start shipping the self-hosted compiler instead of the Bootstrap Compiler.

In this release cycle, we took a detour from making progress on passing more tests, to invest in applying Data-Oriented Design principles to the codebase. This resulted in significant performance improvements.

Despite the fact that .zig source code by default is still compiled in this release using the Bootstrap Compiler, the main driver code is already self-hosted, as well as many features, such as zig cc, C Translation, CPU feature detection, and the Cache System. Improvements made to "stage2" in these areas do in fact affect the main Zig user experience. The bullet points listed here are part of the shared code between both compiler implementations.

Miscellaneous improvements:

Contributors: Andrew Kelley, Jakub Konka, Jacob G-W, Robin Voetter, Joachim Schmidt, Luuk de Gram, Evan Haas, Martin Wickham, Matthew Borkowski, Lee Cannon, Veikka Tuominen, Takeshi Yoneda, Travis Staloch, drew, Ryan Liptak, xackus, Emily Bellows, Jonathan Marler, g-w1, Alex Rønne Petersen, Dmitry Matveyev, J.C. Moyer, Meghan Denny, Michael Dusan, kprotty, Andrew Gutekanst, Daniele Cocca, FnControlOption, Isaac Freund, Kurt Kartaltepe, LemonBoy, Lewis Gaul, Matt Knight, Tom Maenan Read Cutting, Zen1th, jacob gw, vole-dev, Žiga Željko, AODQ, Al Hoang, Christopher Smyth, Dimenus, Exonorid, Frank Denis, Hadrien Dorio, Isaac Freund, Josh Soref, Kenta Iwasaki, Loris Cro, Michal Ziulek, Scibuild, Sebastian Ullrich, Sizhe Zhao, Stephen Gregoratto, Stephen Gutekanst, Stéphan Kochen, Tamas Kenez, Thomas Ives, Vincent Rischmann, daurnimator, pithlessly, travisstaloch, xavier

Parser §

LLVM Backend §

The LLVM backend is now passing 44% of the behavior tests. This is currently the main focus of the self-hosted compiler effort, because when it gets to 100%, we can start shipping the self-hosted compiler instead of the Bootstrap Compiler.

The new LLVM backend has reached a milestone: it can now produce a viable binary for this Tetris clone.

This gives us some early performance measurements. On Andrew's laptop:

zig build-exe src/main.zig -fLLVM -lc -Istb_image-2.22 -lglfw -lepoxy  --main-pkg-path . stb_image-2.22/stb_image_impl.c -fno-stage1

Using the Bootstrap Compiler:

Using the Self-Hosted Compiler:

This time includes:

So this is not a fair benchmark to measure how many lines/second Zig can compile. Even so, it's 1.5x faster and 0.53x as much memory as the Bootstrap Compiler.

C Backend §

The C backend is now passing 22% of the behavior tests. When it gets to 100%, we can delete the Bootstrap Compiler and replace it with the self-hosted compiler converted into C code.

x86 Backend §

The x86 backend is not yet capable of executing the zig test runner, so technically it is passing 0% of the behavior tests.

However basic codegen is already in place, we have an MIR representation, and both rendering to machine code and assembly code is implemented. We are just a few bug fixes and enhancements away from x86 joining the "behavior tests passing" progress bar.

Jakub Konka has started working on this in earnest.

aarch64 Backend §

The aarch64 backend is in a very similar state as the x86 Backend. Joachim Schmidt has been making steady progress.

Self-Hosted Linker §

The main improvements to the self-hosted linker have been to the MachO linker backend by Jakub Konka and to the WebAssembly linker backend by Luuk de Gram.

The most significant milestone for the self-hosted MachO linker is that it is getting battle tested by the community every day, and it can now successfully be used as a drop-in linker for other languages and environments such as C, C++, Objective-C, Objective-C++, Go and Rust. It can also be used to cross-compile code dependent on frameworks from a non-Apple host such as Linux to Apple platforms such as macOS, iOS, etc., with the sysroot provided by the user.

Additionally, the linker was featured at the 2021 Handmade Seattle Conference where Jakub in a short 5 minute video demonstrated the linker's current capabilities when used with the bootstrap compiler (stage1), and showcased a prototype for incremental linking and what developer experience it will unlock when used with the self-hosted compiler (stage2). The demo is available at the Handmade Seattle Conference media website here: The ZLD Linker demo.

Major improvements to the MachO linker backend:

Miscellaneous linker improvements:

C Translation §

In addition to these improvements to zig translate-c, Veikka Tuominen, Evan Haas, and iddev5 have been working on arocc, a C compiler written in Zig. One of the goals of the project is to replace Clang as Zig's C frontend for C translation.

Contributors: Evan Haas, Veikka Tuominen, xackus, Matthew Borkowski, Stéphan Kochen, Andrew Kelley, Tamas Kenez

Cache System §

Zig's cache system is central to its strategy to support many targets, by shipping with only source files and lazily building artifacts as needed.

Shared Cache Locking §

The cache system now drops from exclusive locks to shared locks after an artifact is built. Additionally, when checking the cache, if an exclusive lock cannot be obtained, a shared lock will be obtained instead. Together, this solves a design flaw in the caching system that was causing deadlocks, and allows multiple processes to share the same read-only build artifacts, while still allowing a cache garbage collection utility to operate concurrently with the Zig compiler even as build artifacts are being actively used.

This required new Standard Library functionality: std.fs.File.setLock. The Windows implementation based on using NtLockFile and NtUnlockFile rather than relying on ShareAccess flags and manual polling.

This resolved the deadlock problems reported in #9139 and #9187. It also allowed us to remove another workaround to a deadlock when supplying the same source file multiple times.

Main issue: #7596

Improved Handling of Generated File builtin.zig §

All Zig code is eligible to @import("builtin") which is mapped to a generated file, builtin.zig, based on the target and other settings.

Zig invocations which share the same target settings will generate the same builtin.zig file and thus the path to builtin.zig is in a shared cache folder, and different projects can sometimes use the same file.

In previous versions of Zig, this led to conditions where multiple invocations of zig would race to write this file. If one process wanted to read the file while another process wrote the file, the reading process could observe a truncated or partially written builtin.zig file.

Zig 0.9.0 has the following improvements:

In summary, performance is improved and a race condition crash was fixed (#9439).

Detection of Problematic Timestamps §

One problem with caching systems is that if a file is written, read, and then written again all within a very short period of time, the file system mtime granularity may not tell a difference between those two writes, and so a subsequent read might register an unchanged file, when in fact the file was changed.

To avoid this problem, Zig recognizes the concept of problematic timestamps, which notices at read() time that the current time is so recent that the file system's mtime granularity would not actually tell the difference from the previous write. In such case it marks the mtime as untrustworthy, so that next time the cache system does not rely on the mtime, and is forced to fall back to a file contents hash.

In previous releases of Zig, this problematic timestamp was calculated based on a syscall to get the current time. This was not an accurate measurement of problematic timestamps, because asking the system for the current time did not necessarily correspond to the time that the file system would return, and it had no way of conveying the mtime granularity of the filesystem.

In Zig 0.9.0, the problematic timestamp is learned by writing a temporary file to the cache directory, and reading the mtime from that temporary file. This mtime will have the appropriate granularity to use to check if a timestamp is problematic (i.e. too recent) and should not be trusted.

Thank you to Travis Martin for submitting this improvement (#9930).

Improved Hashing Logic §

Stephen Gutekanst writes:

While investigating slow build times with a large project, I found that the compiler was reading from disk nearly every C source file in my project when rebuilding despite no changes having been made. This accounted for several seconds of time (approx. 20-30% of running zig build without any changes to the sources.)

The cause of this was that comparisons of file mtimes would always fail (the mtime of the file on disk was always newer than that stored in the cache manifest), and so the cache logic would always fall back to byte-for-byte file content comparisons with what is on disk versus in the cache - reading every C source file in my project from disk during each rebuild. Because file contents were the same, a cache hit occurred, and despite the mtime being different the cache manifest would not be updated.

One could reproduce this by building a Zig project so the cache is populated, and then changing mtimes of their C source files to be newer than what is in the cache (without altering file contents.)

The fix was rather simple: always write the updated cache manifest regardless of whether or not a cache hit occurred, because a cache hit doesn't indicate if a manifest is dirty. Luckily, writeManifest already contained logic to determine if a manifest is dirty and becomes no-op if no change to the manifest file is necessary, so the fix was to call writeManifest in the Self-Hosted Compiler even in the case of a cache hit.

Debug Crash Handler §

Martin Wickham added this crash report to the panic function, which has been really nice for internal compiler development. Crashes now look something like this:

$ ./zig-out/bin/zig test  -fLLVM ../test/behavior/eval_stage1.zig 
thread 831982 panic: access of inactive union field
Analyzing ../test/behavior/eval_stage1.zig: eval_stage1.zig:test.inlined loop has array literal with elided runtime scope on first iteration but not second iteration
      %115 = load(%111) node_offset:22:19
      %116 = int(2)
      %117 = cmp_lt(%115, %116) node_offset:22:21
      %118 = as_node(@Ref.bool_type, %117) node_offset:22:21
    > %119 = condbr_inline(%118, {
        %127 = dbg_stmt(23, 9)
        %128 = alloc_inferred() node_offset:23:9
        %133 = block({
          %129 = load(%111) node_offset:23:28
          %130 = cmp_eq(%129, @Ref.zero) node_offset:23:30
          %131 = as_node(@Ref.bool_type, %130) node_offset:23:30
          %132 = condbr(%131, {
            %134 = array_type(@Ref.one, @Ref.i32_type)
            %135 = elem_type(%134) node_offset:23:36
            %136 = coerce_result_ptr(%134, %128)
            %137 = elem_ptr_imm(%136, 0) node_offset:23:43
            %138 = int(2)
            %139 = store_node(%137, %138) node_offset:23:43
            %140 = validate_array_init({
              %137 = elem_ptr_imm(%136, 0) node_offset:23:43
            }) node_offset:23:42
            %143 = break(%133, @Ref.void_value)
          }, {
            %141 = load(%101) node_offset:23:51
            %142 = store_to_block_ptr(%128, %141)
            %144 = break(%133, @Ref.void_value)
          }) node_offset:23:24
        }) node_offset:23:24
        %145 = resolve_inferred_alloc(%128) node_offset:23:9
        %146 = dbg_stmt(24, 9)
        %147 = load(%128) node_offset:24:13
        %148 = ensure_result_non_error(%147) node_offset:24:13
        %149 = break_inline(%120, @Ref.void_value)
      }, {
        %150 = break_inline(%114, @Ref.void_value)
      }) node_offset:22:12
    For full context, use the command
      zig ast-check -t ../test/behavior/eval_stage1.zig

  in ../test/behavior/eval_stage1.zig: eval_stage1.zig:test.inlined loop has array literal with elided runtime scope on first iteration but not second iteration
    > %120 = block_inline({%115..%119}) node_offset:22:12
  in ../test/behavior/eval_stage1.zig: eval_stage1.zig:test.inlined loop has array literal with elided runtime scope on first iteration but not second iteration
    > %114 = block_inline({%120..%126}) node_offset:22:12

/home/andy/Downloads/zig/src/Sema.zig:991:53: 0x3049c86 in Sema.analyzeBody (zig)
                const break_data = datas[break_inst].@"break";
                                                    ^
/home/andy/Downloads/zig/src/Sema.zig:956:56: 0x3049525 in Sema.analyzeBody (zig)
                const break_inst = try sema.analyzeBody(block, inline_body);
                                                       ^
/home/andy/Downloads/zig/src/Sema.zig:956:56: 0x3049525 in Sema.analyzeBody (zig)
                const break_inst = try sema.analyzeBody(block, inline_body);
                                                       ^
/home/andy/Downloads/zig/src/Module.zig:4306:25: 0x301b2c5 in Module.analyzeFnBody (zig)
    _ = sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) {
                        ^
/home/andy/Downloads/zig/src/Compilation.zig:2343:47: 0x2dff765 in Compilation.processOneJob (zig)
                var air = module.analyzeFnBody(decl, func, sema_arena) catch |err| switch (err) {
                                              ^
/home/andy/Downloads/zig/src/Compilation.zig:2261:30: 0x2df229d in Compilation.performAllTheWork (zig)
            try processOneJob(self, work_item, main_progress_node);
                             ^
/home/andy/Downloads/zig/src/Compilation.zig:1872:31: 0x2deddd7 in Compilation.update (zig)
    try self.performAllTheWork();
                              ^
/home/andy/Downloads/zig/src/main.zig:2875:20: 0x2d5812f in updateModule (zig)
    try comp.update();
                   ^
/home/andy/Downloads/zig/src/main.zig:2558:17: 0x2ce4d2e in buildOutputType (zig)
    updateModule(gpa, comp, hook) catch |err| switch (err) {
                ^
/home/andy/Downloads/zig/src/main.zig:216:31: 0x2cca5f6 in mainArgs (zig)
        return buildOutputType(gpa, arena, args, .zig_test);
                              ^
/home/andy/Downloads/zig/src/main.zig:165:20: 0x2cc9350 in main (zig)
    return mainArgs(gpa, arena, args);
                   ^
/home/andy/Downloads/zig/lib/std/start.zig:553:37: 0x3179297 in std.start.callMain (zig)
            const result = root.main() catch |err| {
                                    ^
/home/andy/Downloads/zig/lib/std/start.zig:495:12: 0x2ccc037 in std.start.callMainWithArgs (zig)
    return @call(.{ .modifier = .always_inline }, callMain, .{});
           ^
/home/andy/Downloads/zig/lib/std/start.zig:460:12: 0x2ccbde2 in std.start.main (zig)
    return @call(.{ .modifier = .always_inline }, callMainWithArgs, .{ @intCast(usize, c_argc), c_argv, envp });
           ^
Aborted (core dumped)

Here we can see that there is a bug in the compiler where it tried to access the wrong union field. Not only do we get a stack trace pointing at the union field access, but it tells us the source file, line number, and function that the compiler was analyzing when it crashed, it dumps out the ZIR in text form, pointing to the exact instruction that was being analyzed, and dumps out the analysis stack including the ZIR instructions.

In many cases, seeing this information is enough to completely diagnose the problem, without having to resort to a debugger. It additionally makes it easier for contributors to help out, and it increases the chances of error reports to include enough information to reproduce or diagnose the issue.

Stop Guessing Whether the System Can Execute Binaries §

Previously when using zig run or zig test, Zig would try to guess whether the host system was capable of running the target binaries. Now, it will always try. If it fails, then Zig emits a helpful warning to explain the probable cause:

$ uname -a
Linux ark 5.10.81 #1-NixOS SMP Sun Nov 21 12:46:37 UTC 2021 x86_64 GNU/Linux

$ zig run hello.zig -target aarch64-linux
warning: the host system (x86_64-linux.5.10.81...5.10.81-gnu.2.33) does not appear to be capable of executing binaries from the target (aarch64-linux.3.16...5.5.5-musl)
error: the following command failed to execve with 'InvalidExe':
/home/andy/.cache/zig/o/b61b3c9b78433477f6b5c343ce95631f/hello

$ zig run hello.zig -target x86_64-linux-gnu -lc
warning: the host system does not appear to be capable of executing binaries from the target because the host dynamic linker is located at '/nix/store/z56jcx3j1gfyk4sv7g8iaan0ssbdkhz1-glibc-2.33-56/lib/ld-linux-x86-64.so.2', while the target dynamic linker path is '/lib64/ld-linux-x86-64.so.2'. Consider using --dynamic-linker
error: the following command failed to execve with 'FileNotFound':
/home/andy/.cache/zig/o/f0f8bdc473cbcf7f00bd72f0450102ad/hello

$ zig test behavior.zig -target aarch64-linux 
warning: the host system (x86_64-linux.5.10.81...5.10.81-gnu.2.33) does not appear to be capable of executing binaries from the target (aarch64-linux.3.16...5.5.5-musl). Consider using --test-no-exec or --test-cmd
error: the following command failed with 'InvalidExe':
../test/zig-cache/o/b70989a95e10772fc12652c7f94a5cf5/test zig

$ zig test behavior.zig -target x86_64-linux-gnu -lc
warning: the host system does not appear to be capable of executing binaries from the target because the host dynamic linker is located at '/nix/store/z56jcx3j1gfyk4sv7g8iaan0ssbdkhz1-glibc-2.33-56/lib/ld-linux-x86-64.so.2', while the target dynamic linker path is '/lib64/ld-linux-x86-64.so.2'. Consider using --dynamic-linker, --test-no-exec, or --test-cmd
error: the following command failed with 'FileNotFound':
../test/zig-cache/o/795162652b84a90baa06fbb62db5eb68/test zig

This integrates better when something such as binfmt_misc is installed.

See also the new Executors feature of the Zig Build System.

Now, the default for dynamic libraries (-l or --library) is to only link them if they end up being actually used.

With the Zig CLI, the new options -needed-l or --needed-library can be used to force link against a dynamic library.

With zig cc, this behavior can be overridden with -Wl,--no-as-needed (and restored with -Wl,--as-needed).

We might need to revert this change in order to satisfy the design requirement of having the default be the same on all platforms due to some macOS considerations.

Related issue: #10164

Standard Library §

The Zig standard library is still unstable and mainly serves as a testbed for the language. After the Self-Hosted Compiler is completed, the language stabilized, and Package Manager completed, then it will be time to start working on stabilizing the standard library. Until then, experimentation and breakage without warning is allowed.

Miscellaneous improvements:

Contributors: Andrew Kelley, Robin Voetter, Jakub Konka, kprotty, Ryan Liptak, Lee Cannon, Vincent Rischmann, LemonBoy, Veikka Tuominen, Jacob G-W, Jonathan Marler, Matthew Borkowski, Evan Haas, Felix (xq) Queißner, Takeshi Yoneda, Koakuma, Luuk de Gram, Martin Wickham, Aaron Sikes, Al Hoang, Frank Denis, Isaac Freund, Marc Tiehuis, lucky, Kenta Iwasaki, Matt Chudleigh, Stephen Gregoratto, Travis Staloch, Ali Chraghi, Asherah Connor, Hiroaki Nakamura, Jens Goldberg, Lewis Gaul, Meghan, Michael Dusan, N00byEdge, Ominitay, Tom Maenan Read Cutting, jacob gw, viri, Dmitry Matveyev, Gregory Anders, InKryption, Isaac Freund, Jarred Sumner, Jeremy Fillingim, Malcolm Still, Samadi van Koten, Silver, Sizhe Zhao, Sreehari Sreedev, Stephen von Takach, Sébastien Marie, Travis Martin, codic12, daurnimator, rgreenblatt, xackus, Adam C, Andrew Gutekanst, Arnav Singh, ArtixFox, Asa Zeren, Austin Clements, Aydin Mercan, Ayende Rahien, Biolunar, Björn Linse, Boo, Carlos Zúñiga, Chris Gregory, Chris Heyes, Coleman Broaddus, Daniele Cocca, Dante Catalfamo, Dante Catalfamo, Dustin Taylor, Edward Dean, Ellis Trisk-Grove, Emil Lerch, Exonorid, Fabio Arnold, Felix "xq" Queißner, Filippo Casarin, FnControlOption, Frank Denis, Garrett Squire, Hiroaki Nakamura, HugoFlorentino, Isaac Freund, Isaac Yonemoto, Jakub Dupak, Jan200101, Justin Whear, Kirjastonhoitaja, Klecko, LemonBoy, Luna, Mahdi Khanalizadeh, Matt Knight, Max Hollmann, Michal Ziulek, Miles Alan, Nameless, Nathan Michaels, Nguyễn Gia Phong, Philip Åkesson, Philipp Lühmann, Rohlem, Sebastien Marie, Tau, Thom Chiovoloni, Tizoner, Trioct, William Stein, Zach Banks, Zapolsky Anton, charlieman, chwayne, d18g, fn ⌃ ⌥, g-w1, hadroncfy, jdmichaud, joachimschmidt557, leesongun, mason1920, pfg, protty, purringChaos, tgschultz, tjohnes, viri

Deprecations §

Additional API §

Crypto §

Math §

JSON §

Formatted Printing §

Please note that the formatted printing API is unstable and will very likely break everyone's formatting code before we reach 1.0.

Threading §

Mutex Lock/Unlock §

Thread.Mutex: change API to lock() and unlock().

This is a breaking change. Before, usage looked like this:

const held = mutex.acquire();
defer held.release();

Now it looks like this:

mutex.lock();
defer mutex.unlock();

The Held type was an idea to make mutexes slightly safer by making it more difficult to forget to release an aquired lock. However, this ultimately caused more problems than it solved, when any data structures needed to store a held mutex. Simplify everything by reducing the API down to the primitives: lock() and unlock().

Related issues: #8051 #8246 #10105

Allocgate §

The mem.Allocator interface has changed in a breaking way.

In short summary, here is how to change your code:

The motivation for this change was performance.

This blog post is an excellent piece of technical writing; I couldn't have done a better job myself in these release notes: Allocgate is coming in Zig 0.9, and you will have to change your code

Random Interface §

The rand.Random interface was changed in an equivalent manner to the Allocator interface changes, for the same reason (perf).

Hash Maps §

Logging §

Only 4 Log Levels §

Over the last year of using std.log in practice, it has become clear that having the previous 8 distinct log levels (to match syslog) does more harm than good. It is too subjective which level a given message should have which makes filtering based on log level weaker as not all messages will have been assigned the log level one might expect.

Instead, more granular filtering should be achieved by leveraging the logging scope feature. Filtering based on a combination of scope and log level should be sufficiently powerful for all use-cases.

Note that the Self-Hosted Compiler has already limited itself to 4 distinct log levels for many months and implemented granular filtering based on both log scope and level. This has worked very well in practice.

The four log levels are:

Namespacing of OS Bits §

Since usingnamespace No Longer Affects Identifier Lookup, it helped to introduce proper namespacing to the many OS-specific constants that were essentially ported over from C headers (#9618).

Bug Fixes §

Full list of the 147 bug reports closed during this release cycle.

Note: many bugs were both introduced and resolved within this release cycle.

This Release Contains Bugs §

Zero the Ziguana

Zig has known bugs and even some miscompilations.

Zig is immature. Even with Zig 0.9.0, working on a non-trivial project using Zig will likely require participating in the development process.

When Zig reaches 1.0.0, a new requirement for Tier 1 Support will be 0 known bugs for that target.

A 0.9.1 release is planned.

Zig Build System §

The Zig Build System is invoked via the zig build command, which executes a declarative build.zig script to collect options and describe the graph of steps, and then provides options to execute those steps.

Although it is already essential to nearly every Zig project, the Zig Build System is still experimental and unstable. As a build system, stability is especially important, but cannot occur until the language stabilizes. Language stability is the next area of focus on the Roadmap.

Miscellaneous improvements:

Contributors: Andrew Kelley, Felix (xq) Queißner, Lee Cannon, Aaron Sikes, Jonathan Marler, Isaac Freund, Jakub Konka, Martin Wickham, Evan Haas, Ryan Liptak, Tom Maenan Read Cutting, Veikka Tuominen, Andrew Gutekanst, Asa Zeren, Edward Dean, Ellis Trisk-Grove, Jacob G-W, Jan200101, Jarred Sumner, Kenta Iwasaki, LemonBoy, Luuk de Gram, Matt Knight, N00byEdge, Ominitay, Takeshi Yoneda

Generated Files as Inputs §

There has been a breaking change which breaks all projects using std.build.Pkg.

The build system now uses std.build.FileSource in more places instead of paths. This supports the use case of using generated files anywhere the build system expects a file, such as when creating an executable or a library.

Usage example:

const std = @import("std");

const TemplateStep = @import("src/TemplateStep.zig");

pub fn build(b: *std.build.Builder) void {
    const target = b.standardTargetOptions(.{});
    const mode = b.standardReleaseOptions();

    // An arbitrary step that generates a output file
    const template_step = TemplateStep.create(b, std.build.FileSource{
        .path = "example/layout.ztt",
    });

    const exe = b.addExecutable("demo", "example/main.zig");
    exe.addPackage(std.build.Pkg{
        .name = "template",
        .path = template_step.getFileSource(), // we can now add the generated file also as a package source
    });
    exe.setTarget(target);
    exe.setBuildMode(mode);
    exe.install();
}

Build Options §

Previously, there was a function addBuildOption which could be used to create a basic build_options.zig file which could then be imported into zig code and used for conditional compilation. This API had several problems:

Now this API is reworked (this is a breaking change) with the following improvements (#9623):

Example usage:

const client = b.addSharedLibrary("client", "src/client/zig/client_main.zig", .unversioned);
client.setTarget(.{
    .cpu_arch = .wasm32,
    .os_tag = .freestanding,
});
client.addPackagePath("shared", "src/shared/index.zig");

const server_options = b.addOptions();
server_options.addOptionArtifact("client_wasm_path", client);
server_options.addOption(u32, "mem_leak_frames", mem_leak_frames);
server_options.addOption(bool, "support_mp3", support_mp3);

const server = b.addExecutable("groovebasin", "src/server/server_main.zig");
server.setTarget(target);
server.setBuildMode(mode);
server.addPackagePath("shared", "src/shared/index.zig");

server.addOptions("build_options", server_options);
server.install();

In this example, we see that the server_main.zig file will be able to @import("build_options").client_wasm_path and have a comptime string that contains the file path of the client WebAssembly file produced by Zig.

Additionally, there were various bug fixes for addOption (#10016).

Executors §

zig build now has the following additional flags:

  -fdarling,  -fno-darling     Integration with system-installed Darling to
                               execute macOS programs on Linux hosts
                               (default: no)
  -fqemu,     -fno-qemu        Integration with system-installed QEMU to execute
                               foreign-architecture programs on Linux hosts
                               (default: no)
  --glibc-runtimes [path]      Enhances QEMU integration by providing glibc built
                               for multiple foreign architectures, allowing
                               execution of non-native programs that link with glibc.
  -frosetta,  -fno-rosetta     Rely on Rosetta to execute x86_64 programs on
                               ARM64 macOS hosts. (default: no)
  -fwasmtime, -fno-wasmtime    Integration with system-installed wasmtime to
                               execute WASI binaries. (default: no)
  -fwine,     -fno-wine        Integration with system-installed Wine to execute
                               Windows programs on Linux hosts. (default: no)

These enable integration with system-installed programs which can provide the ability to test cross-compiled build artifacts. For example:

$ uname
Linux
$ uname -m
x86_64
$ zig init-exe
info: Created build.zig
info: Created src/main.zig
info: Next, try `zig build --help` or `zig build run`
$ zig build test # run the tests natively
All 1 tests passed.
$ zig build test -Dtarget=aarch64-linux # compiles but does not run the tests
$ zig build test -Dtarget=aarch64-linux -fqemu # now it runs the tests!
All 1 tests passed.
$ zig build test -Dtarget=x86_64-windows -fwine # we can even test our windows code on linux 😎
All 1 tests passed.

Toolchain §

LLVM 13 §

This release of Zig upgrades to LLVM 13.

Good news: we noticed something like a 12% improvement in compilation speed when we upgraded Zig from LLVM 12 to 13.

musl 1.2.2 §

Zero the Ziguana

Zig ships with the source code to musl. When the musl C ABI is selected, Zig builds static musl from source for the selected target. Zig also supports targeting dynamically linked musl which is useful for Linux distributions that use it as their system libc, such as Alpine Linux.

Musl has not tagged a new release since Zig 0.8.0, so the version shipped with Zig remains at 1.2.2. However, there has been a major improvement when targeting dynamically linked musl libc.

Zig now generates a more accurate libc.so for cross compiling purposes. Previously, the way it worked is that we used a script to process the output of objdump --dynamic-syms /path/to/musl/libc.so and generate a libc.s file, which could then be lazily compiled by Zig into libc.so for the requested target when cross compiling.

The problem is that the input libc.so file we used was musl built dynamically for x86_64. While this is almost correct for other architectures besides x86_64, it is not completely correct, leading to bugs such as #8896.

The updated script now inputs musl built for 7 different architectures:

These are produced by passing zig cc in as the CC environment variable when building musl (instructions).

Next, the script identifies:

Using this information, the script creates a libc.S file, which is shipped along with the Zig compiler. The file is only 104 KB because it uses the preprocessor in order to support the few differences between architectures. For example, if you execute Zig like this:

zig build-exe hello.c -target aarch64-linux-musl -dynamic

Zig will, under the hood, assemble libc.S with -DPTR64 -DARCH_aarch64, producing a libc.so file that can be passed to the linker, accurately modeling the libc.so that will be found on the target.

The preprocessor directives in libc.S are based on excluding symbols, so even when targeting an architecture that is missing from that list of 7, will work fine; it's just that there may be extra symbols that shouldn't be there, and if there happened to be any special symbols that only existed for that one architecture, those will be missing. It will still be a small improvement to expand that list of 7 to more targets. Regardless, the changes made in this release are strictly an improvement when compared with prior status quo.

glibc 2.34 §

Zig gains the ability to target glibc 2.34 in addition to the other 46 glibc versions.

There was a major improvement to targeting glibc when cross compiling.

Zig supports targeting every version of glibc for any target architecture. Previously, in order to accomplish this, Zig had only the information from the .abilist files from the latest verison of glibc:

$ cat ../lib/libc/glibc/*.txt | wc -c
205219
$ cat ../lib/libc/glibc/*.txt | xz | wc -c
22976

The information required to do this took up 200 KB installation size / 22 KB tarball size, and it had the following problems (which caused several bugs):

Now there is a new ziglang/glibc-abi-tool that produces an improved dataset. The project is quite interesting; if you are curious to learn more, check out the README of that repository which explains in detail the strategy and encoding.

With the new dataset:

$ cat abilists | wc -c
169421
$ cat abilists | xz | wc -c
24880

The new data that Zig ships with is 165 KB installation size / 24 KB tarball size, and all the above issues are solved.

When a Zig user requests to cross compile for glibc, Zig uses this abilists file to create, for the requested glibc version and CPU architecture:

...which are then assembled into:

These files are cached and placed on the linker line when cross compiling. Thanks to this improved dataset, they now accurately model the requested version of glibc.

If we naively shipped every version of the .abilist files:

$ cat (find glibc/ -name "*.abilist") | wc -c
37041906
$ cat (find glibc/ -name "*.abilist") | xz | wc -c
205036

...it would have been 35 MiB installation size, 200 KB tarball size. So we have effectively achieved a compression ratio of 219:1 by implementing a bespoke encoding of this information.

Native Version Detection §

When Zig is instructed to build for the native OS (which is the default, unless a -target argument is provided) on a glibc system, Zig first checks its own executable ELF file to determine if it is dynamically linked. If it is, it introspects its own dynamically loaded libraries to find libc, and find out if it is glibc, and if so, what version it is.

When the Zig compiler is statically linked, it falls back to inspecting the /usr/bin/env ELF file to determine the native glibc version, by checking the DT_RUNPATH, and then calling readlink() on the libc.so file, because typically the symlink will have e.g. libc-2.33.so in the name, revealing the glibc version.

Until now, if the file did not have a DT_RUNPATH, Zig would give up and fall back to the default version of glibc, which was 2.17.

Fortunately, this information is also in readlink() of ld.so, which is available as the "INTERP" file path in the ELF file. Zig now looks for e.g. ld-2.33.so on the symlink data for the dynamic linker, which makes Zig correctly detect the native glibc version in more cases.

In theory a more complete solution would also look at /etc/ld.so.cache if necessary, and finally fall back to some hard coded paths, in order to resolve the location of libc.so, in order to do this readlink() trick on the resulting path. You can find that flow chart with man ld.so. But it looks like this logic will be enough to get a correct answer in all real world cases. Of course if we find out that is not the case, we can expand the detection logic.

This has been tested on Debian Buster and glibc-based Void Linux.

See #6469 for more details.

mingw-w64 9.0.0 §

Zig ships with the source code to mingw-w64. When targeting *-windows-gnu and linking against libc, Zig builds mingw-w64 from source for the selected target.

The mingw-w64 project has not tagged a new release since Zig 0.8.0, so the version shipped with Zig remains at 9.0.0. However, there have been improvements related to mingw-w64 integration:

libunwind §

Fixed unwinding through libunwind stack frames (#9591).

libcxx §

Disabled redundant new/delete definitions which are already provided by libcxxabi.

zig cc §

Please be aware that there are still open zig cc issues. However, these are use cases that never worked in the first place, making them bugs and enhancements, not regressions.

Contributors: Andrew Kelley, Vincent Rischmann

zig c++ §

zig c++ is equivalent to zig cc with an added -lc++ parameter, but I made a separate heading here because I realized that some people are not aware that Zig supports compiling C++ code and providing libc++ too!

#include <iostream>
int main() {
    std::cout << "Hello World!" << std::endl;
    return 0;
}
$ zig c++ -o hello hello.cpp
$ ./hello
Hello World!

Cross-compiling too, of course:

$ zig c++ -o hello hello.cpp -target riscv64-linux
$ qemu-riscv64 ./hello
Hello World!

One thing that trips people up when they use this feature is that the C++ ABI is not stable across compilers, so always remember the rule: You must use the same C++ compiler to compile all your objects and static libraries. This is an unfortunate limitation of C++ which Zig can never fix.

zig fmt §

Contributors: Jacob G-W, jdmichaud, tjohnes, chwayne

zig ar §

Fixed argument forwarding to LLVM on Windows, fixing zig ar on Windows.

Objective-C and Objective-C++ §

Zig now supports compiling Objective-C files:

Foo.h

#import <Foundation/Foundation.h>

@interface Foo : NSObject

- (NSString *)name;

@end

Foo.m

#import "Foo.h"

@implementation Foo

- (NSString *)name
{
      NSString *str = [[NSString alloc] initWithFormat:@"Zig"];
      return str;
}

@end

test.m

#import "Foo.h"
#import <assert.h>

int main(int argc, char *argv[])
{
  @autoreleasepool {
      Foo *foo = [[Foo alloc] init];
      NSString *result = [foo name];
      assert([result isEqualToString:@"Zig"]);
      return 0;
  }
}
$ zig run test.m Foo.m -I. -framework Foundation

Of course the zig cc CLI works as well:

$ zig cc -o test test.m Foo.m -I. -framework Foundation

Likewise, Zig now supports compiling Objective-C++ files. In our example, if we rename test.m to test.mm, Foo.m to Foo.mm, and update test.mm like so:

test.mm

#import "Foo.h"
#import <assert.h>

int main(int argc, char *argv[])
{
  @autoreleasepool {
      Foo *foo = [[Foo alloc] init];
      NSString *result = [foo name];
      std::cout << "Hello from C++ and " << [result UTF8String];
      assert([result isEqualToString:@"Zig"]);
      return 0;
  }
}
$ zig run test.mm Foo.mm -I. -framework Foundation
Hello from C++ and Zig

Of course the zig c++ CLI works as well:

$ zig c++ -o test test.mm Foo.mm -I. -framework Foundation

Contributors: Stephen Gutekanst, Jakub Konka, Andrew Kelley

compiler-rt §

compiler-rt is the library that provides, for example, 64-bit integer multiplication for 32-bit architectures which do not have a machine code instruction for it. In the GNU world, it's called libgcc.

Unlike most compilers, which depend on a binary build of compiler-rt being installed alongside the compiler, Zig builds compiler-rt on-the-fly, from source, as needed for the target platform. This release saw some improvements to Zig's compiler-rt implementation (#1290).

With all these additions, we nearly have a fully complete compiler-rt implementation!

Contributors: Andrew Kelley, Jan Philipp Hafer, Jacob G-W, Jonathan Marler, LemonBoy, J.C. Moyer, Kenta Iwasaki, Stephen Gutekanst, Veikka Tuominen, jdmichaud

Performance Tracking §

The gotta-go-fast repository contains a set of benchmarks that are now run for each commit on our Continuous Integration server, providing a set of graphs for various metrics that we track over time.

Performance Dashboard Zero the Ziguana

These have already helped diagnose performance regressions. As an example, the pull request that implemented Allocgate accidentally deleted a call to free() in the GeneralPurposeAllocator. This did not actually cause any tests to fail, but on the performance dashboard, it was clear that a bunch more memory was getting used.

As Zig matures, we will add more benchmarks to this system, particularly for measuring compilation speed of various users' projects, so that we can pay attention to how changes to the codebase over time are affecting performance characteristics.

Thank you to Austin Rude for improving the user interface and display of the graphs.

Continuous Integration §

Zig Software Foundation is now paying Hetzner for a 100 euros/month bare metal x86_64 machine which operates ci.ziglang.org.

We switched away from Azure for x86_64 Linux CI runs, gaining the following benefits:

Big, big thank you to Michael Dusan who volunteered his time to set up and maintain this. Please consider donating to the ZSF so that we can pay more people like him for their valuable time.

Still on the to-do list is migrating these over to the new system via QEMU or some other OS virtualization software:

And finally we still have these remaining:

Unfortunately until we solve these last three, we will continue to experience flaky CI failures due to OOM. That is, until we ship the Self-Hosted Compiler, which uses significantly less memory, side-stepping this problem.

Other miscellaneous news:

Roadmap §

Ziggy the Ziguana

The primary goal of the 0.9.0 release cycle was self-hosting the compiler. At this time, 44% of the behavior tests are passing, with the percent rising quickly.

The major theme of the 0.10.0 release cycle will be stabilizing the language, creating a first draft of the language specification, and self-hosting the compiler.

Some upcoming milestones in the next release cycle:

Here are the steps for Zig to reach 1.0:

  1. Finish the Self-Hosted Compiler.
  2. Stabilize the language. No more Language Changes after this.
  3. Complete the language specification first draft.
  4. Implement the official package manager.
  5. Stabilize the Standard Library. That means to add any missing functionality, audit the existing functionality, curate it, re-organize everything, and fix all the bugs.
  6. Go one full release cycle without any breaking changes.
  7. Finally we can tag 1.0.

Package Manager Status §

Having a package manager built into the Zig compiler is a long-anticipated feature. Zig 0.9.0 does not have this feature.

If the package manager works well, people will use it, which means building Zig projects will involve compiling more lines of Zig code, which means the Zig compiler must get faster, better at incremental compilation, and better at resource management.

Therefore, the package manager depends on finishing the Self-Hosted Compiler, since it is planned to have these improved performance characteristics, while the Bootstrap Compiler is not planned to have them.

Accepted Proposals §

If you want more of a sense of the direction Zig is heading, you can look at the set of accepted proposals.

Thank You Sponsors! §

Ziggy the Ziguana

Special thanks to those who sponsor Zig. Because of you, Zig is driven by the open source community, rather than the goal of making profit. In particular, these fine folks sponsor Zig for $50/month or more: