Skip to content

zig

4 posts with the tag “zig”

Zig_allcator

Zig 语言不干预堆内存分配, 没有隐藏的内存分配,没有new关键字或其他任何使用堆分配器的语言功能,整个堆都是由库或者用户代码而非语言本身所管理的。

Zig 标准库提供了一中分配内存的模式,这允许程序员精准选择标准库中内存如何完成分配,在标准库中不会背着你偷偷分配内存。

Zig 提供的分配器 allocator 有:

释放内存惯用模式是使用deferdefer 在退出作用域时指定给定的代码,『作用域退出』包括到达作用域的结尾或从作用域返回。

Zig 的 defer 类似于 Go 的 defer,但存在一个主要区别。在 Zig 中,defer 将在其包含作用域的末尾运行。在 Go 中,defer 是在包含函数的末尾运行。

通过使用defer,指定分配器在退出作用域时释放分配的内存。

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var arr = try allocator.alloc(usize, try getRandomCount());
defer allocator.free(arr);
for (0..arr.len) |i| {
arr[i] = i;
}
std.debug.print("{any}\n", .{arr});

这是最基本的分配器,线程安全且无锁,每次内存的分配(allocation)和释放(free),都会触发一次直接的系统调用,它会直接要求操作系统分配或者释放整页内存, 即使是单直接内存分配也可能导致保留数千字节的内存。由于通过系统调用要求操作系统 OS 分配内存,这对于速读来说也是及其低效的。

const std = @import("std");
const expect = std.testing.expect;
test "allocation" {
const allocator = std.heap.page_allocator;
const memory = try allocator.alloc(u8, 100);
defer allocator.free(memory);
try expect(memory.len == 100);
try expect(@TypeOf(memory) == []u8);
}
const page_allocator: type = if (builtin.target.isWasm())
Allocator{
.ptr = undefined,
.vtable = &WasmPageAllocator.vtable,
}
else if (builtin.target.os.tag == .plan9)
Allocator{
.ptr = undefined,
.vtable = &SbrkAllocator(std.os.plan9.sbrk).vtable,
}
else if (builtin.target.os.tag == .freestanding)
root.os.heap.page_allocator
else
Allocator{
.ptr = undefined,
.vtable = &PageAllocator.vtable,
};

这是一种通用的、线程安全的分配器,可以作为程序的主分配器。这是一个安全的分配器,可以防止双重释放( double-free )、使用后释放( use-after-free) 还可以检测泄漏。可以通过它的配置结构(.{}),将安全检测和线程安全关闭掉:

test "GPA" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const bytes = try allocator.alloc(u8, 100);
defer allocator.free(bytes);
}

Zig Wasm Memory Canvas

展示HTML canvas、wasm memory 和 zig 交互的一个小示例。

这个示例来自https://github.com/daneelsan/minimal-zig-wasm-canvas,而这示例本来修改来自https://wasmbyexample.dev/examples/reading-and-writing-graphics/reading-and-writing-graphics.rust.en-us.html。对于当前这个本身,它如大多数 zig 项目一样,zig 命令是你唯一的依赖项。

main.zig 定义了一个全局的 8X8 像素数组: checkerboard_buffer。并导出三个函数:getCheckerboardBufferPointer()getCheckerboardSize()colorCheckerboard(...), getCheckerboardBufferPointer() 返回一个指向checkerboard_buffer开始处的指针,这将在script.js 被使用。getCheckerboardSize() 返回棋盘格子数量。 colorCheckerboard(...)函数根据传入的颜色参数,来改变棋盘格的颜色。

main.zig 通过build.zig被编译成 wasm模块 checkerboard.wasm

index.html 定义了一个canvas,并指定样式:image-rendering: pixelated; width: 50%,这是因为main.zig是定义的一个8x8 象素的棋盘格,所以需要通过 CSS属性image-rendering 来对 canvas 画出的图像进行放大显示,以获得一个清晰的棋盘格来清楚的展示颜色的变化。

script.js 文件创建了一个名为memoryWebAssembly.Memory ,它存储的是WebAssembly实例所访问内存的原始字节码。下一步是使用WebAssembly.instantiateStreaming()方法编译和示例化获取到的wasm模块,返回的一个Promise对象,它包含一个instanace字段(WebAssembly.instanace) , 它保存有从checkerboard.wasm 导出的所有方法。

接下来是:

  • WebAssembly.Memory memory 创建一个Uint8Array数组:wasmMemoryArray

  • 获取到index.html中定义的canvas checkerboard, 并使用getCheckerboardSize()获取到的棋盘格数来指定canvas 的widthheight,接着使用canvas的context 创建一个 ImageData 对象:ImageData

  • 在循环中更新棋盘格的颜色:

    • 传入RGB颜色参数,调用colorCheckerboard(...),这会修改checkerboard_buffer中保存的值
    • checkerboard_buffer中的内容,是一个段wasmMemoryArray,可以通过getCheckerboardBufferPointer获取内存起始地址 和 8 _ 8 _ 4 字节的偏移长度来获取
    • 将获取到的内容,放入到imageData
    • imageData 写入到canvas

build.zig 指定的为一个构建目标是:wasm32-freestanding-musl

用于构建项目的最新zig版本是:

Terminal window
$ zig version
0.12.0-dev.817+54e7f58fc

构建wasm 模块,运行:

Terminal window
$ zig build
$ ls zig-out/lib/
zig-wasm-canvas.wasm

注意: 为了运行将内存导入到 WASM 二进制文件中,构建时需要指定--import-memory参数

在项目目录上,启动一个服务:

Terminal window
$ python3 -m http.server

这时在你的浏览器中浏览:localhost:8000,就可以看到棋盘格在改变颜色。

Zig in Webassembly

Zig 是一种通用编程语言和工具链,用于维护健壮、优化和可重用的软件,Zig 支持开箱即用的 Webassembly 构建,Zig 的实现是通过使用 LLVM 来提供编译目标,对Webassembly System Interface(WASI) 的支持尚处于积极开发中,可以参见官方文档

Zig 从 0.4.0 开始提供对 WebAssembly 的实现

WebAssembly 是一种运行在现代网络浏览器中的新型代码,并且提供新的性能特性和效果。它设计的目的不是为了手写代码而是为诸如 C、C++和 Rust 等低级源语言提供一个高效的编译目标。

Webassembly 1.0 已经在4个主要浏览器引擎中发布Roadmap

为了进一步推动模块化 WebAssembly 生态系统,Mozilla、Fastly、英特尔和红帽公司携手成立了ByteCode Alliance(字节码联盟)。ByteCode Alliance 是一个非营利组织,致力于在 WebAssembly 和 WebAssembly System Interface(WASI) 等标准的基础上创建安全的新软件基础。

作为 W3C WebAssembly Community Group 中的一项开放标准,WebAssembly 是为下列目标而生的:

  • 快速、高效、可移植——通过利用常见的硬件能力,WebAssembly 代码在不同平台上能够以接近本地速度运行。
  • 可读、可调试——WebAssembly 是一门低阶语言,但是它有确实有一种人类可读的文本格式(其标准即将得到最终版本),这允许通过手工来写代码,看代码以及调试代码。
  • 保持安全——WebAssembly 被限制运行在一个安全的沙箱执行环境中。像其他网络代码一样,它遵循浏览器的同源策略和授权策略。
  • 不破坏网络——WebAssembly 的设计原则是与其他网络技术和谐共处并保持向后兼容。

更多参见: MDN Webassembly概念

1 life.zig 导出了三个函数

// Advance the world by strong mutation
export fn advance() u32 {
....
WORLD.cells = cell_buf;
return num_changed;
}
export fn set_cell(index: u32) void {
if (index > NUM_CELLS) return;
WORLD.cells[index] = .Alive;
}
export fn get_char(index: u32) u32 {
if (index > NUM_CELLS) return '◻';
return switch (WORLD.cells[index]) {
.Dead => {
return '◻';
},
.Alive => {
return '◼';
},
};
}

2 在 life.js 中会调用这些 WASM 导出的方法

// Load WebAssembly module `life.wasm`
// https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/instantiateStreaming
Game = await WebAssembly.instantiateStreaming(
fetch("life.wasm"), importObject
)
set_cell = Game.instance.exports.set_cell;
advance = Game.instance.exports.advance;
get_char = Game.instance.exports.get_char;
wasm_loaed = true
...

3 在 index.html 加载life.js

<html lang="en">
<head>
<title>Life Demo</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<input type="button" id="button" label="button" value="BUTTON" />
<pre id="life_pre"></pre>
</body>
<script src="life.js"></script>
</html>

以上这些就是使用Zig 创建 WebAssemble 程序的代码。(非常感谢sleibrock/zigtoys)

4 构建 life.wasm

Terminal window
$ zig build-lib life.zig -target wasm32-freestanding -dynamic -rdynamic -O ReleaseSmall

更多构建说明参见 Ziglang.org

5 运行

启动一个本地web server:

Terminal window
$ python3 -m http.server

在浏览器中,浏览:http://localhost:8000 demo

  1. Webassembly
  2. Webassembly developer guide
  3. MDN Webassembly
  4. Zig language Reference
  5. WebAssembly with Zig, Part 1
  6. WebAssembly With Zig, Pt. II
  7. ByteCode Alliance

Zig初体验

遇到一个小众而又趣的开发语言Zig,目前还在开发中,初步实现自举,目前没有发布1.0版本,当前发布的最新版本是0.10.1。

Zig 可以在项目直接加入 C 代码实现混编,Zig 可以作为 C/C++ 语言的编译器。

Zig 不支持宏编程,但支持comptime关键字,在编译时运行代码和惰性求值,编写通用代码或是进行元编程。

Zig 没有像 Rust 那样实现内存安全,可 Zig 也没有 Rust 那么复杂。

Zig 内置测试功能,还自带很完备的命令工具,这点是不是和 Go 很像?

Terminal window
$ zig
Usage: zig [command] [options]
Commands:
build Build project from build.zig
init-exe Initialize a `zig build` application in the cwd
init-lib Initialize a `zig build` library in the cwd
ast-check Look for simple compile errors in any set of files
build-exe Create executable from source or object files
build-lib Create library from source or object files
build-obj Create object from source or object files
fmt Reformat Zig source into canonical form
run Create executable and run immediately
test Create and run a test build
translate-c Convert C code to Zig code
ar Use Zig as a drop-in archiver
cc Use Zig as a drop-in C compiler
c++ Use Zig as a drop-in C++ compiler
dlltool Use Zig as a drop-in dlltool.exe
lib Use Zig as a drop-in lib.exe
ranlib Use Zig as a drop-in ranlib
objcopy Use Zig as a drop-in objcopy
env Print lib path, std path, cache directory, and version
help Print this help and exit
libc Display native libc paths file or validate one
targets List available compilation targets
version Print version number and exit
zen Print Zen of Zig and exit
General Options:
-h, --help Print command-specific usage

⚡ 一种简单的语言

专注于调试你的应用程序,而不是调试你的编程语言知识

  • 没有隐式控制流
  • 没有隐式内存分配
  • 没有预处理器,没有宏

⚡ 编译期代码执行

基于编译期代码执行和惰性求值的全新元编程方法

  • 编译期调用任意函数
  • 在没有运行时开销的情况下,将类型作为值进行操作
  • 编译期模拟目标架构

⚡ 用Zig维护代码

逐步改善你的C/C++/Zig代码库

  • 将Zig作为一个零依赖的,支持开箱即用交叉编译的C/C++编译器
  • 利用 zig build在所有平台上创建一个一致的开发环境
  • 在C/C++项目中添加一个Zig编译单元,跨语言LTO默认启用

官网文章 深入了解 从系统编程的角度对 Zig 进行深入的功能了解,有了 C++、D 和 Rust,为什么还需要 Zig? 介绍了Zig 同其他语言的区别。

ziglings 项目可以通过修补和调试一系列带有故障的小程序来学习 Zig 。

ziglearn.org 有一份 Zig 在线学习教程。

创建hello.zig 文件,内容:

const std = @import("std");
pub fn main() void {
std.debug.print("Hello, {s}!\n", .{"World"});
}

使用命令zig run hello.zig 来构建和运行:

Terminal window
$ zig run main.zig
Hello, World!

也可以通过Zig 内置命令来完成,运行:

Terminal window
mkdir hello-world
cd hello-world
zig init-exe

输出:

Terminal window
info: Created build.zig
info: Created src/main.zig
info: Next, try `zig build --help` or `zig build run`

运行 zig build run 应该会编译成可执行文件并运行,最终结果将会是:

Terminal window
$ zig build run
All your codebase are belong to us.
Run `zig build test` to run the tests.

官方支持的一个language server 是 zls ,目前主流编辑器都有Zig 的语法高亮支持、自动补全、定义跳转等。

Zig 自带构建相关命令行工具:

  • zig build 通过build.zig 构建项目(build project)
  • zig init-exe 初始化一个zig build 应用程序项目
  • zig init-lib 初始化一个 zig build 类库项目

Zig 提供四种编译模式,Debug模式是默认模式,因为它需要的编译时间最短。

运行时安全优化
DebugYesNo
ReleaseSafeNoYes, Speed
ReleaseSmallNoYes, Size
ReleaseFastNoYes, Speed

这些可以在zig runzig test 命令中,通过使用参数 -O ReleaseSafe, -O ReleaseSmall-O ReleaseFast 来启用。

zig build-exe, zig build-lib, zig-obj 分别用于构建输出 可执行文件、lib文件、obj文件,这些命令都接受源文件和参数。

几个常见参数:

  • -fsingle-threaded 断言二进制文件是单线程的, 这会导致如互斥锁之类的线程安全措施变成空操作。
  • -fstrip 从二进制文件中移除调试信息
  • --dynamic它与 zig build-lib 结合使用以输出动态/共享库。

如对上面的hello.zig 执行命令 zig build-exe ./main.zig -O ReleaseSafe -fstrip -fsingle-threaded 生成一个24k可执行的文件,而使用命令zig build-exe ./main.zig -O ReleaseSmall -fstrip -fsingle-threaded生成的可执行文件只用14k。

hello.zig
const std = @import("std");
pub fn main() void {
std.debug.print("Hello, {s}!\n", .{"World"});
}

默认情况下,Zig 会根据你的 CPU 和操作系统进行编译。这可以通过-target参数进行覆盖,可以通过下面的命令将hello编译到64位arm linux 平台:

Terminal window
zig build-exe ./main.zig -O ReleaseSmall -fstrip -fsingle-threaded -target aarch64-linux

可以对一下的CPU架构交叉编译:

  • x86_64
  • arm
  • aarch64
  • i386
  • riscv64
  • wasm32

可以对一下操作系统进行交叉编译:

  • linux
  • macos
  • windows
  • freebsd
  • netbsd
  • dragonfly
  • UEFI

-target 参数格式是: <arch><sub>-<os>-<abi>,通过运行命令zig targets 可以找到可以支持的架构、操作系统、CPU、ABI详细信息。

Terminal window
$ zig targets | jq "keys"
[
"abi",
"arch",
"cpuFeatures",
"cpus",
"glibc",
"libc",
"native",
"os"
]
$ zig targets | jq ".native"
{
"triple": "x86_64-macos.13.3.1...13.3.1-none",
"cpu": {
"arch": "x86_64",
"name": "skylake",
"features": [
"64bit",
"adx",
"aes",
"allow_light_256_bit",
"avx",
"avx2",
"bmi",
"bmi2",
"clflushopt",
"cmov",
"crc32",
"cx16",
......

如: 对于M1架构的 MacOS 可以使用aarch64-macos, Intel x86 CPU版 MacOS 使用x86_64-macos-none,x86 64 位CPU Windows 使用x86-windows-gnu,x86 架构64位CPU Linux 使用x86-linux-gnu:

Terminal window
$ cat main.zig
const std = @import("std");
pub fn main() void {
std.debug.print("Hello, {s}!\n", .{"World"});
}
$ zig build-exe ./main.zig -O ReleaseSmall -fstrip -fsingle-threaded -target x86_64-macos
$ ./main
Hello, World!
$ zig build-exe ./main.zig -O ReleaseSmall -fstrip -fsingle-threaded -target x86_64-windows-gnu
$ ls
main.zig main.exe
Terminal window
$ cat hello.c
#include <stdio.h>
int main(int argc, char **argv) {
fprintf(stderr, "Hello, World!\n");
return 0;
}
Terminal window
$ zig build-exe ./hello.c -O ReleaseSmall -fstrip
$ ./hello
Hello, World!

连 1.0 版本都没有,Uber 为什么会采用这样一项新技术? Uber 使用 Zig 来 编译其 C/C++代码,与其他工具链相比,zig-cc 提供的 C/C++工具链的主要优势是:glibc 版本可配制与 macOS 交叉编译。但 Uber 没有任何使用 zig-the-language 的计划。

  1. make self-hosted the default compiler #12368
  2. How Uber Uses Zig
  3. Bazel C/C++ toolchain for cross-compiling C/C++ programs
  4. Zig Language Reference
  5. ziglearn.org
  6. Mastering project managment in Zig