From 567512f08a993ccb265c019509879ddcbc001364 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Thu, 6 Mar 2025 19:10:36 +0800 Subject: [PATCH 01/12] update code --- build/0.11.zig | 6 +++--- course/code/14/build_system/basic/build.zig.zon | 3 ++- course/code/14/build_system/cli/build.zig.zon | 3 ++- course/code/14/build_system/docs/build.zig.zon | 3 ++- course/code/14/build_system/embedfile/build.zig.zon | 3 ++- course/code/14/build_system/externalfile/build.zig.zon | 3 ++- course/code/14/build_system/lib/build.zig.zon | 3 ++- course/code/14/build_system/options/build.zig.zon | 3 ++- course/code/14/build_system/step/build.zig.zon | 3 ++- course/code/14/build_system/system_lib/build.zig.zon | 3 ++- course/code/14/build_system/test/build.zig.zon | 3 ++- course/code/14/build_system/tinytetris/build.zig.zon | 3 ++- .../code/14/import_dependency_build/pkg1/build.zig.zon | 3 ++- .../code/14/import_dependency_build/pkg2/build.zig.zon | 3 ++- course/code/14/import_vcpkg/build.zig.zon | 3 ++- course/code/14/package_management_exporter/build.zig.zon | 3 ++- course/code/14/package_management_importer/build.zig.zon | 9 ++++----- 17 files changed, 37 insertions(+), 23 deletions(-) diff --git a/build/0.11.zig b/build/0.11.zig index 7131097d..f0f476e9 100644 --- a/build/0.11.zig +++ b/build/0.11.zig @@ -22,9 +22,9 @@ pub fn build(b: *Build) void { // open dir var dir = std.fs.openIterableDirAbsolute(full_path, .{}) catch |err| { - log.err("open 11 path failed, err is {}", .{err}); - std.os.exit(1); - }; + log.err("open 11 path failed, err is {}", .{err}); + std.os.exit(1); + }; defer dir.close(); // make a iterate for path diff --git a/course/code/14/build_system/basic/build.zig.zon b/course/code/14/build_system/basic/build.zig.zon index c20a6b36..8553e004 100644 --- a/course/code/14/build_system/basic/build.zig.zon +++ b/course/code/14/build_system/basic/build.zig.zon @@ -6,11 +6,12 @@ // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. - .name = "basic", + .name = .basic, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", + .fingerprint = 0x907975534fe79435, // This field is optional. // This is currently advisory only; Zig does not yet do anything diff --git a/course/code/14/build_system/cli/build.zig.zon b/course/code/14/build_system/cli/build.zig.zon index 47890afe..09eef71d 100644 --- a/course/code/14/build_system/cli/build.zig.zon +++ b/course/code/14/build_system/cli/build.zig.zon @@ -6,11 +6,12 @@ // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. - .name = "cli", + .name = .cli, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", + .fingerprint = 0x48f6513c15de5e44, // This field is optional. // This is currently advisory only; Zig does not yet do anything diff --git a/course/code/14/build_system/docs/build.zig.zon b/course/code/14/build_system/docs/build.zig.zon index 6e0b1aeb..a40db6b3 100644 --- a/course/code/14/build_system/docs/build.zig.zon +++ b/course/code/14/build_system/docs/build.zig.zon @@ -6,11 +6,12 @@ // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. - .name = "docs", + .name = .docs, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", + .fingerprint = 0x51572bb73db54779, // This field is optional. // This is currently advisory only; Zig does not yet do anything diff --git a/course/code/14/build_system/embedfile/build.zig.zon b/course/code/14/build_system/embedfile/build.zig.zon index 1c03131e..9fab9793 100644 --- a/course/code/14/build_system/embedfile/build.zig.zon +++ b/course/code/14/build_system/embedfile/build.zig.zon @@ -6,11 +6,12 @@ // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. - .name = "embedfile", + .name = .embedfile, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", + .fingerprint = 0x3e6a9e8b250bb664, // This field is optional. // This is currently advisory only; Zig does not yet do anything diff --git a/course/code/14/build_system/externalfile/build.zig.zon b/course/code/14/build_system/externalfile/build.zig.zon index a7f05b8c..bded66c1 100644 --- a/course/code/14/build_system/externalfile/build.zig.zon +++ b/course/code/14/build_system/externalfile/build.zig.zon @@ -6,11 +6,12 @@ // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. - .name = "externalfile", + .name = .externalfile, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", + .fingerprint = 0x61de81227ca0d23c, // This field is optional. // This is currently advisory only; Zig does not yet do anything diff --git a/course/code/14/build_system/lib/build.zig.zon b/course/code/14/build_system/lib/build.zig.zon index 11f8801f..6ca3a749 100644 --- a/course/code/14/build_system/lib/build.zig.zon +++ b/course/code/14/build_system/lib/build.zig.zon @@ -6,11 +6,12 @@ // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. - .name = "library", + .name = .library, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", + .fingerprint = 0xa18098bc77aad8e8, // This field is optional. // This is currently advisory only; Zig does not yet do anything diff --git a/course/code/14/build_system/options/build.zig.zon b/course/code/14/build_system/options/build.zig.zon index da6d0c21..b3a7f357 100644 --- a/course/code/14/build_system/options/build.zig.zon +++ b/course/code/14/build_system/options/build.zig.zon @@ -6,11 +6,12 @@ // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. - .name = "options", + .name = .options, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", + .fingerprint = 0xd035fa8769f41b1a, // This field is optional. // This is currently advisory only; Zig does not yet do anything diff --git a/course/code/14/build_system/step/build.zig.zon b/course/code/14/build_system/step/build.zig.zon index e33098fe..a2461217 100644 --- a/course/code/14/build_system/step/build.zig.zon +++ b/course/code/14/build_system/step/build.zig.zon @@ -6,11 +6,12 @@ // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. - .name = "step", + .name = .step, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", + .fingerprint = 0x43b9fe3c8067cab6, // This field is optional. // This is currently advisory only; Zig does not yet do anything diff --git a/course/code/14/build_system/system_lib/build.zig.zon b/course/code/14/build_system/system_lib/build.zig.zon index 20e26b90..6f9b6047 100644 --- a/course/code/14/build_system/system_lib/build.zig.zon +++ b/course/code/14/build_system/system_lib/build.zig.zon @@ -6,11 +6,12 @@ // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. - .name = "system_lib", + .name = .system_lib, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", + .fingerprint = 0x64b791172db5c549, // This field is optional. // This is currently advisory only; Zig does not yet do anything diff --git a/course/code/14/build_system/test/build.zig.zon b/course/code/14/build_system/test/build.zig.zon index 1e2c5b3a..92b35441 100644 --- a/course/code/14/build_system/test/build.zig.zon +++ b/course/code/14/build_system/test/build.zig.zon @@ -6,11 +6,12 @@ // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. - .name = "test", + .name = .ttest, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", + .fingerprint = 0x334b1002be4b456c, // This field is optional. // This is currently advisory only; Zig does not yet do anything diff --git a/course/code/14/build_system/tinytetris/build.zig.zon b/course/code/14/build_system/tinytetris/build.zig.zon index 9949b15e..a8f670af 100644 --- a/course/code/14/build_system/tinytetris/build.zig.zon +++ b/course/code/14/build_system/tinytetris/build.zig.zon @@ -6,11 +6,12 @@ // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. - .name = "tinytetris", + .name = .tinytetris, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", + .fingerprint = 0x5f03916d4d995c27, // This field is optional. // This is currently advisory only; Zig does not yet do anything diff --git a/course/code/14/import_dependency_build/pkg1/build.zig.zon b/course/code/14/import_dependency_build/pkg1/build.zig.zon index b7f3f040..6eceed4b 100644 --- a/course/code/14/import_dependency_build/pkg1/build.zig.zon +++ b/course/code/14/import_dependency_build/pkg1/build.zig.zon @@ -1,6 +1,7 @@ .{ - .name = "pkg1", + .name = .pkg1, .version = "0.0.0", + .fingerprint = 0x759ba61b105d16f9, .dependencies = .{ .pkg2 = .{ // path 为本地包的路径 diff --git a/course/code/14/import_dependency_build/pkg2/build.zig.zon b/course/code/14/import_dependency_build/pkg2/build.zig.zon index a6367e25..35893f62 100644 --- a/course/code/14/import_dependency_build/pkg2/build.zig.zon +++ b/course/code/14/import_dependency_build/pkg2/build.zig.zon @@ -1,6 +1,7 @@ .{ - .name = "pkg2", + .name = .pkg2, .version = "0.0.0", + .fingerprint = 0xec92f7a1a7362798, .dependencies = .{}, .paths = .{ "build.zig", diff --git a/course/code/14/import_vcpkg/build.zig.zon b/course/code/14/import_vcpkg/build.zig.zon index e34533ee..ec0454bf 100644 --- a/course/code/14/import_vcpkg/build.zig.zon +++ b/course/code/14/import_vcpkg/build.zig.zon @@ -6,11 +6,12 @@ // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. - .name = "import_vcpkg", + .name = .import_vcpkg, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", + .fingerprint = 0x75583b9623cebdcb, // This field is optional. // This is currently advisory only; Zig does not yet do anything diff --git a/course/code/14/package_management_exporter/build.zig.zon b/course/code/14/package_management_exporter/build.zig.zon index b41a721b..3f2fcacd 100644 --- a/course/code/14/package_management_exporter/build.zig.zon +++ b/course/code/14/package_management_exporter/build.zig.zon @@ -1,9 +1,10 @@ // #region package_management .{ // 包名字 - .name = "exporter", + .name = .exporter, // 包版本 .version = "0.0.0", + .fingerprint = 0x6a125ce7eaa53f2, // 包所包含的源文件,一般用于在对外提供包时才使用,还是建议养成写清楚paths的习惯 .paths = .{ "build.zig", diff --git a/course/code/14/package_management_importer/build.zig.zon b/course/code/14/package_management_importer/build.zig.zon index 3a5fbba7..022eb0bc 100644 --- a/course/code/14/package_management_importer/build.zig.zon +++ b/course/code/14/package_management_importer/build.zig.zon @@ -1,17 +1,16 @@ // #region package_management .{ // 包名字 - .name = "importer", + .name = .importer, // 包版本 .version = "0.0.0", + .fingerprint = 0x64e883e88dde22e2, // 包依赖 .dependencies = .{ // 包依赖项的名字 .@"tarball-exporter" = .{ - // url 为包的下载地址 - .url = "https://github.com/zigcc/zig-msgpack/archive/refs/tags/0.0.5.tar.gz", - // hash 为包的哈希值,但不是对整个包的哈希值,而是对包中的所有文件的哈希值 - .hash = "12207ec1296852c17c0424b5e650003bd9a3c4f35e9d0ab586d39ea0ab812c2d9f2c", + .url = "https://github.com/zigcc/zig-msgpack/archive/refs/tags/0.0.7.tar.gz", + .hash = "zig_msgpack-0.0.7-evvueE3MAADy-2EAgCGUYIf1tHC9-z4n2sDIldvTZcY8", }, .@"path-exporter" = .{ // path 为本地包的路径 From d95e61d3e67175ef9201fb9854607296d3aaeeec Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Thu, 6 Mar 2025 20:25:40 +0800 Subject: [PATCH 02/12] remove `@fence` --- course/advanced/atomic.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/course/advanced/atomic.md b/course/advanced/atomic.md index 214d5546..9f050eef 100644 --- a/course/advanced/atomic.md +++ b/course/advanced/atomic.md @@ -112,16 +112,6 @@ outline: deep 强原子的比较与交换操作,如果目标指针是给定值,那么赋值为参数的新值,并返回 null,否则仅读取值返回。 -### [`@fence`](https://ziglang.org/documentation/master/#fence) - -函数原型: - -```zig -@fence(order: AtomicOrder) void -``` - -用于创建一个内存屏障,防止某些类型的内存重新排序,具体细节可以查看内存屏障的相关信息。 - ## `std.atomic` 包 ### 原子数据结构 From f5988b5babc87cfa329898a792caf2503672db03 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Thu, 6 Mar 2025 21:00:17 +0800 Subject: [PATCH 03/12] docs: code switch `0.14.0` --- course/code/release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/course/code/release b/course/code/release index 3cacc0b9..719486ac 120000 --- a/course/code/release +++ b/course/code/release @@ -1 +1 @@ -12 \ No newline at end of file +./14 \ No newline at end of file From f646b28339b99c6b9a169a664a1267b1f4d26317 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Fri, 7 Mar 2025 11:37:11 +0800 Subject: [PATCH 04/12] map zig `0.13.0` --- course/code/13 | 1 + 1 file changed, 1 insertion(+) create mode 120000 course/code/13 diff --git a/course/code/13 b/course/code/13 new file mode 120000 index 00000000..5fdac560 --- /dev/null +++ b/course/code/13 @@ -0,0 +1 @@ +./12 \ No newline at end of file From 037c15525a8019d111e804775443be3e7dea0405 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Fri, 7 Mar 2025 12:35:00 +0800 Subject: [PATCH 05/12] fix: code runner --- build.zig | 1 + build/0.13.zig | 2 +- build/0.15.zig | 101 +++++++++++++++++++++++++++++++++++++++++++++++++ course/code/15 | 1 + 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 build/0.15.zig create mode 120000 course/code/15 diff --git a/build.zig b/build.zig index cf243fce..ca62e6ad 100644 --- a/build.zig +++ b/build.zig @@ -10,6 +10,7 @@ pub fn build(b: *Build) void { 12 => @import("build/0.12.zig").build(b), 13 => @import("build/0.13.zig").build(b), 14 => @import("build/0.14.zig").build(b), + 15 => @import("build/0.15.zig").build(b), else => @compileError("unknown zig version"), } } diff --git a/build/0.13.zig b/build/0.13.zig index d74d7e81..2461beb4 100644 --- a/build/0.13.zig +++ b/build/0.13.zig @@ -6,7 +6,7 @@ const log = std.log.scoped(.For_0_13_0); const args = [_][]const u8{ "zig", "build" }; -const version = "release"; +const version = "13"; const relative_path = "course/code/" ++ version; diff --git a/build/0.15.zig b/build/0.15.zig new file mode 100644 index 00000000..2e8f27a6 --- /dev/null +++ b/build/0.15.zig @@ -0,0 +1,101 @@ +const std = @import("std"); +const Build = std.Build; +const ChildProcess = std.process.Child; + +const log = std.log.scoped(.For_0_14_0); + +const args = [_][]const u8{ "zig", "build" }; + +const version = "15"; + +const relative_path = "course/code/" ++ version; + +pub fn build(b: *Build) void { + // get target and optimize + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + var lazy_path = b.path(relative_path); + + const full_path = lazy_path.getPath(b); + + // open dir + var dir = std.fs.openDirAbsolute(full_path, .{ .iterate = true }) catch |err| { + log.err("open 14 path failed, err is {}", .{err}); + std.process.exit(1); + }; + defer dir.close(); + + // make a iterate for release ath + var iterate = dir.iterate(); + + while (iterate.next()) |val| { + if (val) |entry| { + // get the entry name, entry can be file or directory + const output_name = std.mem.trimRight(u8, entry.name, ".zig"); + if (entry.kind == .file) { + + // connect path + const path = std.fs.path.join(b.allocator, &[_][]const u8{ relative_path, entry.name }) catch |err| { + log.err("fmt path for examples failed, err is {}", .{err}); + std.process.exit(1); + }; + + // build exe + const exe = b.addExecutable(.{ + .name = output_name, + .root_source_file = b.path(path), + .target = target, + .optimize = optimize, + }); + exe.linkLibC(); + + // add to default install + b.installArtifact(exe); + + // build test + const unit_tests = b.addTest(.{ + .root_source_file = b.path(path), + .target = target, + .optimize = optimize, + }); + + // add to default install + b.getInstallStep().dependOn(&b.addRunArtifact(unit_tests).step); + } else if (entry.kind == .directory) { + + // build child process + var child = ChildProcess.init(&args, b.allocator); + + // build cwd + const cwd = std.fs.path.join(b.allocator, &[_][]const u8{ + full_path, + entry.name, + }) catch |err| { + log.err("fmt path for examples failed, err is {}", .{err}); + std.process.exit(1); + }; + + // open entry dir + const entry_dir = std.fs.openDirAbsolute(cwd, .{}) catch unreachable; + entry_dir.access("build.zig", .{}) catch { + log.err("not found build.zig in path {s}", .{cwd}); + std.process.exit(1); + }; + + // set child cwd + // this api maybe changed in the future + child.cwd = cwd; + + // spawn and wait child process + _ = child.spawnAndWait() catch unreachable; + } + } else { + // Stop endless loop + break; + } + } else |err| { + log.err("iterate examples_path failed, err is {}", .{err}); + std.process.exit(1); + } +} diff --git a/course/code/15 b/course/code/15 new file mode 120000 index 00000000..719486ac --- /dev/null +++ b/course/code/15 @@ -0,0 +1 @@ +./14 \ No newline at end of file From ca4f9699f17dff75e187ea38b303feffbc0e0da5 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sat, 15 Mar 2025 16:09:17 +0800 Subject: [PATCH 06/12] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=8D=87=E7=BA=A7=E6=8C=87=E5=8D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- course/.vitepress/sidebar.ts | 17 +- course/update/0.14.0-description.md | 0 course/update/upgrade-0.14.0.md | 856 ++++++++++++++++++++++++++++ 3 files changed, 869 insertions(+), 4 deletions(-) create mode 100644 course/update/0.14.0-description.md create mode 100644 course/update/upgrade-0.14.0.md diff --git a/course/.vitepress/sidebar.ts b/course/.vitepress/sidebar.ts index d0b95a70..3c917b2d 100644 --- a/course/.vitepress/sidebar.ts +++ b/course/.vitepress/sidebar.ts @@ -217,13 +217,14 @@ export default [ collapsed: true, items: [ { - text: "0.12.0 升级指南", - link: "/update/upgrade-0.12.0", + text: "0.14.0 升级指南", + link: "/update/upgrade-0.14.0", }, { - text: "0.12.0 版本说明", - link: "/update/0.12.0-description", + text: "0.14.0 版本说明", + link: "/update/0.14.0-description", }, + { text: "0.13.0 升级指南", link: "/update/upgrade-0.13.0", @@ -232,6 +233,14 @@ export default [ text: "0.13.0 版本说明", link: "/update/0.13.0-description", }, + { + text: "0.12.0 升级指南", + link: "/update/upgrade-0.12.0", + }, + { + text: "0.12.0 版本说明", + link: "/update/0.12.0-description", + }, ], }, { diff --git a/course/update/0.14.0-description.md b/course/update/0.14.0-description.md new file mode 100644 index 00000000..e69de29b diff --git a/course/update/upgrade-0.14.0.md b/course/update/upgrade-0.14.0.md new file mode 100644 index 00000000..aad6072a --- /dev/null +++ b/course/update/upgrade-0.14.0.md @@ -0,0 +1,856 @@ +--- +outline: deep +showVersion: false +--- + +本篇文档将介绍如何从 `0.13.0` 版本升级到 `0.14.0`。 + +## 语法变动 + +### 标记 `switch` + +Zig 官方团队接受了一个[新的提案](https://github.com/ziglang/zig/issues/8220),该提案允许对 `switch` 语句进行标记,并允许其成为 `continue` 语句跳转的目标。此处的 `continue` 语句接受单个操作数(类似 `break` 可以从块或循环返回一个值),该值将直接替换`switch` 表达式的操作数。 + +这个新的语法糖就类似在循环中指定变量作为 `switch` 操作数一样,示例如下: + +```zig +// 以下两个单元测试等价,使用新的语法糖显著减少代码的长度 +// 可以简单理解为类似 c 语言中的 `goto` +test "labeled switch" { + foo: switch (@as(u8, 1)) { + 1 => continue :foo 2, + 2 => continue :foo 3, + 3 => return, + 4 => {}, + else => unreachable, + } + return error.Unexpected; +} + +test "emulate labeled switch" { + var op: u8 = 1; + while (true) { + switch (op) { + 1 => { + op = 2; + continue; + }, + 2 => { + op = 3; + continue; + }, + 3 => return, + 4 => {}, + else => unreachable, + } + break; + } + return error.Unexpected; +} +``` + +新的语法糖有时会更易于理解,例如在实现有限状态自动机时,可以编写 `continue :fsa new_state` 来表示状态转换。 + +同时现在可以使用带标签的 `break` 从 `switch` 中跳出来,此时可以得到 `switch` 表达式的操作数。如果是没有标记的 `break`,那么只会对 `for` 或者 `while` 生效。 + +与普通的 `switch` 语句不同,带有一个或多个 `continue` 目标的标记 `switch` 语句不会在编译时隐式求值(这类似于循环的行为)。然而,与循环一样,可以通过在 `comptime` 上下文中求值这样的表达式来强制进行编译时求值。 + +#### 代码生成属性 + +这种语言结构旨在生成有助于 CPU 预测 `switch` 各个 `case` 之间分支的代码,从而提高热循环中的性能,特别是那些调度指令、评估有限状态自动机(FSA)或执行类似基于 `case` 的评估的循环。为了实现这一点,生成的代码可能与直观预期的不同。 + +如果 `continue` 的操作数在编译时已知,那么它可以被翻译为一个无条件分支到相关的 `case`。这样的分支是完全可预测的,因此通常执行速度非常快。 + +如果操作数在运行时已知,那么每个 `continue` 可以变成一个单独的条件分支(理想情况下通过共享跳转表)回到同一组潜在的分支目标。这种模式的优势在于它通过提供不同的分支指令来帮助 CPU 的分支预测器,这些指令可以与不同的预测数据相关联。例如,在评估 FSA 时,如果 case `a` 很可能会跟随 case `b`,而 case `c` 很可能会跟随 case `d`,那么分支预测器可以使用 `switch` case 之间的直接跳转来更准确地预测控制流,而基于循环的降级会导致状态调度“折叠”成单个间接分支或类似的情况,从而阻碍分支预测。 + +这种降级可能会增加代码大小,相比于简单的“循环中的 `switch`”降级,任何 Zig 实现当然可以自由地按照自己的意愿降级这种语法,只要遵守语言语义。然而,官方的 ZSF 编译器实现将尝试匹配上述降级,特别是在 `ReleaseFast` 构建模式下。 + +更多见相关 PR:[Updating Zig's tokenizer to take advantage of this feature resulted in a 13% performance boost.](https://github..com/ziglang/zig/pull/21367)。 + +### 声明字面量 + +Zig 0.14.0 扩展了“**enum literal**”语法 (`.foo`),引入了一项新功能,称为“**decl literals**”。现在,枚举字面量 `.foo` 不一定指代枚举,而是可以使用[结果位置语义(Result Location Semantics)](https://ziglang.org/documentation/0.14.0/#Result-Location-Semantics)引用目标类型上的任何声明。例如,考虑以下示例: + +```zig +const S = struct { + x: u32, + const default: S = .{ .x = 123 }; +}; +test "decl literal" { + const val: S = .default; + try std.testing.expectEqual(123, val.x); +} +const std = @import("std"); +``` + +由于 `val` 的初始化表达式对应的结果类型是 `S`,因此初始化实际上等同于 `S.default`。这在初始化结构体字段时特别有用,可以避免再次指定类型: + +```zig +const S = struct { + x: u32, + y: u32, + const default: S = .{ .x = 1, .y = 2 }; + const other: S = .{ .x = 3, .y = 4 }; +}; +const Wrapper = struct { + val: S = .default, +}; +test "decl literal initializing struct field" { + const a: Wrapper = .{}; + try std.testing.expectEqual(1, a.val.x); + try std.testing.expectEqual(2, a.val.y); + const b: Wrapper = .{ .val = .other }; + try std.testing.expectEqual(3, b.val.x); + try std.testing.expectEqual(4, b.val.y); +} +const std = @import("std"); +``` + +这也可以帮助避免 [Faulty Default Field Values](https://ziglang.org/documentation/0.14.0/#Faulty-Default-Field-Values),可以看下面的例子: + +```zig +/// `ptr` 指向 `[len]u32`. +pub const BufferA = extern struct { ptr: ?[*]u32 = null, len: usize = 0 }; +// 以上给出的默认值是想它默认为空 +var empty_buf_a: BufferA = .{}; +// 不过这样做实际上是违背的开发规范,实际上你可以这样写: +var bad_buf_a: BufferA = .{ .len = 10 }; +// 这样处理并不安全,通过声明字面量可以实现方便并且安全的表示值 + +/// `ptr` 指向 `[len]u32`. +pub const BufferB = extern struct { + ptr: ?[*]u32, + len: usize, + pub const empty: BufferB = .{ .ptr = null, .len = 0 }; +}; +// 以一种更简单的方式创建一个新的空buffer +var empty_buf_b: BufferB = .empty; +// 不会再出现莫名其妙的字段覆盖! +// 如果我们要指定值,那么就需要都指定值,这会使错误更容易暴露出来: +var bad_buf_b: BufferB = .{ .ptr = null, .len = 10 }; +``` + +许多现有的字段默认值使用可能更适合通过名为 default 或 empty 或类似的声明来处理,以确保数据不变性不会因覆盖单个字段而被破坏。 + +声明字面量还支持函数调用,如下所示: + +```zig +const S = struct { + x: u32, + y: u32, + fn init(val: u32) S { + return .{ .x = val + 1, .y = val + 2 }; + } +}; +test "call decl literal" { + const a: S = .init(100); + try std.testing.expectEqual(101, a.x); + try std.testing.expectEqual(102, a.y); +} +const std = @import("std"); +``` + +这种语法在初始化结构体字段时也很有用。它还支持通过 `try` 调用返回错误联合的函数。以下示例结合使用这些功能来初始化一个围绕 `ArrayListUnmanaged` 的薄包装器: + +```zig +const Buffer = struct { + data: std.ArrayListUnmanaged(u32), + fn initCapacity(allocator: std.mem.Allocator, capacity: usize) !Buffer { + return .{ .data = try .initCapacity(allocator, capacity) }; + } +}; +test "initialize Buffer with decl literal" { + var b: Buffer = try .initCapacity(std.testing.allocator, 5); + defer b.data.deinit(std.testing.allocator); + b.data.appendAssumeCapacity(123); + try std.testing.expectEqual(1, b.data.items.len); + try std.testing.expectEqual(123, b.data.items[0]); +} +const std = @import("std"); +``` + +声明字面量的引入伴随着一些标准库的变化。特别是,包括 `ArrayListUnmanaged` 和 `HashMapUnmanaged` 在内的非托管容器不应再使用 `.{}` 进行默认初始化,因为这里的默认字段值违反了上述指导原则。相反,它们应该使用其 `empty` 声明进行初始化,这可以通过声明字面量方便地访问: + +```zig +const Buffer = struct { + foo: std.ArrayListUnmanaged(u32) = .empty, +}; +test "default initialize Buffer" { + var b: Buffer = .{}; + defer b.foo.deinit(std.testing.allocator); + try b.foo.append(std.testing.allocator, 123); + try std.testing.expectEqual(1, b.foo.items.len); + try std.testing.expectEqual(123, b.foo.items[0]); +} +const std = @import("std"); +``` + +类似地,`std.heap.GeneralPurposeAllocator` 现在应该使用其 `.init` 声明进行初始化。 + +这些数据结构的过时默认字段值将在下一个发布周期中移除。 + +#### 字段和声明不可重名 + +Zig `0.14.0` 引入了一项限制,即容器类型(结构体、联合体、枚举和不透明类型)不能有同名的字段和声明(`const`/`var`/`fn`)。添加此限制是为了处理 `MyEnum.foo` 是查找声明还是枚举字段这一问题的歧义(这一问题因声明字面量而加剧)。 + +通常,通过遵循标准命名约定可以避免这种情况: + +```zig +const Foo = struct { + Thing: Thing, + const Thing = struct { + Data: u32, + }; +}; +``` + +⬇️ + +```zig +const Foo = struct { + thing: Thing, + const Thing = struct { + data: u32, + }; +}; +``` + +这一限制的一个好处是,文档注释现在可以明确地引用字段名称,从而使这些引用成为可以点击的超链接。 + +### `@splat` 支持数组 + +Zig `0.14.0` 扩展了 `@splat` 内置函数,不仅适用于向量,还适用于数组,这在将数组默认初始化为常量值时非常有用。 + +例如,结合声明字面量,我们可以优雅地初始化一个 "color" 值的数组: + +```zig +const Rgba = struct { + r: u8, + b: u8, + g: u8, + a: u8, + pub const black: Rgba = .{ .r = 0, .g = 0, .b = 0, .a = 255 }; +}; +var pixels: [width][height]Rgba = @splat(@splat(.black)); +``` + +操作数可以在编译时已知或运行时已知。此外,该内置函数还可以用于初始化以哨兵值结尾的数组。 + +```zig +const std = @import("std"); +const assert = std.debug.assert; +const expect = std.testing.expect; +test "initialize sentinel-terminated array" { + // the sentinel does not need to match the value + const arr: [2:0]u8 = @splat(10); + try expect(arr[0] == 10); + try expect(arr[1] == 10); + try expect(arr[2] == 0); +} +test "initialize runtime array" { + var runtime_known: u8 = undefined; + runtime_known = 123; + // 操作数是运行时可知的,那么返回一个运行时的数组 + const arr: [2]u8 = @splat(runtime_known); + try expect(arr[0] == 123); + try expect(arr[1] == 123); +} +test "initialize zero-length sentinel-terminated array" { + var runtime_known: u8 = undefined; + runtime_known = 123; + const arr: [0:10]u8 = @splat(runtime_known); + // 操作数在运行时已知,但由于数组长度为零,结果在编译时已知。 + comptime assert(arr[0] == 10); +} +``` + +### 全局变量可以互相引用 + +现在这段代码是有效的: + +```zig +const std = @import("std"); +const expect = std.testing.expect; + +const Node = struct { + next: *const Node, +}; + +const a: Node = .{ .next = &b }; +const b: Node = .{ .next = &a }; + +test "example" { + try expect(a.next == &b); + try expect(b.next == &a); +} +``` + +### `@export` 使用指针 + +此版本的 Zig 简化了 `@export` 内置函数。在之前的 Zig 版本中,这个内置函数的第一个操作数在语法上似乎是要导出的值,这个值被限制为局部变量或容器级声明的标识符或字段访问。这种系统限制过多,而且在语法上令人困惑且不一致;导出常量编译时已知的值是合理的,而这种用法暗示了值被导出,但实际上其地址才是相关的信息。为了解决这个问题,`@export` 现在有了一个新的用法,与 `@extern` 非常相似;它的第一个操作数是一个指针,指向要导出的数据。在大多数情况下,解决这个问题只需添加一个 `&` 操作符。 + +```zig +const foo: u32 = 123; +test "@export" { + @export(foo, .{ .name = "bar" }); +} +``` + +⬇️ + +```zig +const foo: u32 = 123; +test "@export" { + @export(&foo, .{ .name = "bar" }); +} +``` + +### `@branchHint` 替换 `@setCold` + +在高性能代码中,有时希望向优化器提示条件的哪个分支更可能被执行;这可以生成更高效的机器代码。一些语言通过在布尔条件上添加 "likely" 注释来实现这一点;例如,GCC 和 Clang 实现了 `__builtin_expect` 函数。Zig `0.14.0` 引入了一种机制来传达此信息:新的 `@branchHint(comptime hint: std.builtin.BranchHint)` 内置函数。这个内置函数不是修改条件,而是作为块中的第一个语句出现,以传达控制流是否可能到达相关块。 + +例如: + +```zig +fn warnIf(cond: bool, message: []const u8) void { + if (cond) { + @branchHint(.unlikely); // we expect warnings to *not* happen most of the time! + std.log.warn("{s}", message); + } +} +const std = @import("std"); +``` + +`BranchHint` 类型如下: + +```zig +pub const BranchHint = enum(u3) { + /// Equivalent to no hint given. + none, + /// This branch of control flow is more likely to be reached than its peers. + /// The optimizer should optimize for reaching it. + likely, + /// This branch of control flow is less likely to be reached than its peers. + /// The optimizer should optimize for not reaching it. + unlikely, + /// This branch of control flow is unlikely to *ever* be reached. + /// The optimizer may place it in a different page of memory to optimize other branches. + cold, + /// It is difficult to predict whether this branch of control flow will be reached. + /// The optimizer should avoid branching behavior with expensive mispredictions. + unpredictable, +}; +``` + +除了作为条件之后块的第一个语句外,`@branchHint` 也允许作为任何函数的第一个语句。期望是优化器可以将可能性信息传播到包含这些调用的分支;例如,如果某个控制流分支总是调用一个标记为 `@branchHint(.unlikely)` 的函数,那么优化器可以假设该分支不太可能被执行。 + +`BranchHint` 包含 `.cold` ,这导致旧的 `@setCold` 功能已经多余,`@setCold` 已被移除。在大多数情况下,迁移非常简单:只需将 `@setCold(true)` 替换为 `@branchHint(.cold)`: + +```zig +fn foo() void { + @setCold(true); + // ... +} +``` + +⬇️ + +```zig +fn foo() void { + @branchHint(.cold); + // ... +} +``` + +但是,需要注意 `@branchHint` 必须是封闭块(函数)中的第一个语句。这一限制在 `@setCold` 中不存在,因此非常规的用法可能需要额外做点小改动: + +```zig +fn foo(comptime x: u8) void { + if (x == 0) { + @setCold(true); + } + // ... +} +``` + +⬇️ + +```zig +fn foo(comptime x: u8) void { + @branchHint(if (x == 0) .cold else .none); + // ... +} +``` + +### 移除 `@fence` + +在 Zig `0.14` 中,`@fence` 已被移除。原本提供 `@fence` 是为了与 C11 内存模型保持一致,但它通过修改所有先前和未来原子操作的内存排序来使语义复杂化。这会产生[难以在检测器中建模的不可预见的约束](https://github.com/google/sanitizers/issues/1415)。fence 操作可以通过升级原子内存排序或添加新的原子操作来替代。 + +`@fence` 的最常见用例可以通过利用更强的内存排序或引入新的原子变量来替代。 + +#### StoreLoad 屏障 + +最常见的用例是 `@fence(.seq_cst)`。这主要用于确保对不同原子变量的多个操作之间的一致顺序。 + +例如: + +| thread-1 | thread-2 | +|-------------------------|-------------------------| +| store X // A | store Y // C | +| fence(seq_cst) // F1 | fence(seq_cst) // F2 | +| load Y // B | load X // D | + +目标是确保要么 `load X (D)` 看到 `store X (A)`,要么 `load Y (B)` 看到 `store Y (C)`。这一对顺序一致的栅栏通过两个不变[1](https://en.cppreference.com/w/cpp/atomic/memory_order#Strongly_happens-before:~:text=for%20every%20pair%20of%20atomic%20operations%20A%20and%20B%20on%20an%20object%20M%2C%20where%20A%20is%20coherence%2Dordered%2Dbefore%20B%3A)[2](https://en.cppreference.com/w/cpp/atomic/memory_order#Strongly_happens-before:~:text=if%20a%20memory_order_seq_cst%20fence%20X%20happens%2Dbefore%20A%2C%20and%20B%20happens%2Dbefore%20a%20memory_order_seq_cst%20fence%20Y%2C%20then%20X%20precedes%20Y%20in%20S.)来保证这一点。 + +现在 `@fence` 已被删除,还有其他方法可以实现这种关系: + +- 将所有相关的存储和加载(A、B、C 和 D)设为 `SeqCst`,将它们全部包含在总顺序中。 +- 将存储操作(A/C)设为 `Acquire`,并将其匹配的加载操作(D/B)设为 `Release`。从语义上讲,这意味着将它们升级为读-修改-写操作,这可以实现这样的排序。加载操作可以替换为非变异的 RMW 操作,即 `fetchAdd(0)` 或 `fetchOr(0)`。 + +像 LLVM 这样的优化器可能会在内部将其简化为 `@fence(.seq_cst)` + `load`。 + +#### 条件屏障 + +fence 的另一个用例是分别使用 `Acquire` 或 `Release` 有条件地与先前或未来的原子操作创建同步关系。 + +一个简单示例是原子引用计数器: + +```zig +fn inc(counter: *RefCounter) void { + _ = counter.rc.fetchAdd(1, .monotonic); +} + +fn dec(counter: *RefCounter) void { + if (counter.rc.fetchSub(1, .release) == 1) { + @fence(.acquire); + counter.deinit(); + } +} +``` + +在 `fetchSub(1)` 中的加载操作只需要在最后一次引用计数递减时为 `Acquire`,以确保之前的递减发生在 `deinit()` 之前。这里的 `@fence(.acquire)` 使用 `fetchSub(1)` 的加载部分创建了这种关系。 + +如果没有 `@fence` ,这里有两种方法: + +- 无条件地通过 fence 的排序来加强所需的原子操作。 + +```zig +if (counter.rc.fetchSub(1, .acq_rel) == 1) { +``` + +- 有条件地复制所需的存储或加载,并按照栅栏的顺序进行 + +```zig +if (counter.rc.fetchSub(1, .release) == 1) { + _ = counter.rc.load(.acquire); +``` + +`Acquire` 将与 `rc` 修改顺序中的最长释放序列同步,使所有先前的递减操作发生在 `deinit()` 之前。 + +#### 同步外部操作 + +`@fence` 最不常见的用法是为程序员无法控制的原子操作(例如外部函数调用)提供额外的同步。在这种情况下使用 `@fence` 依赖于隐式函数具有不理想的弱排序的原子操作。 + +理想情况下,隐式函数应该对用户可访问,他们可以简单地在源代码中增加排序。但如果这不可能,最后的手段是引入一个原子变量来模拟栅栏的屏障。例如: + +| thread-1 | thread-2 | +|-----------------------------|-----------------------------| +| queue.push() | e = signal.listen() | +| fence(.seq_cst) | fence(.seq_cst) | +| signal.notify() | if queue.empty(): e.wait() | + +| thread-1 | thread-2 | +|-----------------------------|-----------------------------| +| queue.push() | e = signal.listen() | +| fetchAdd(0, .seq_cst) | fetchAdd(0, .seq_cst) | +| signal.notify() | if queue.empty(): e.wait() | + +### `packed` 结构体相等性 + +现在可以直接比较 `packed` 结构体,而无需通过 @bitCast 转换为底层整数类型。 + +```zig +const std = @import("std"); +const expect = std.testing.expect; + +test "packed struct equality" { + const S = packed struct { + a: u4, + b: u4, + }; + const x: S = .{ .a = 1, .b = 2 }; + const y: S = .{ .b = 2, .a = 1 }; + try expect(x == y); +} +``` + +### 原子 `packed` 结构体 + +现在可以在原子操作中使用 `packed` 结构体,而无需通过 @bitCast 转换为底层整数类型。 + +```zig +const std = @import("std"); +const expect = std.testing.expect; + +test "packed struct atomics" { + const S = packed struct { + a: u4, + b: u4, + }; + var x: S = .{ .a = 1, .b = 2 }; + const y: S = .{ .a = 3, .b = 4 }; + const prev = @atomicRmw(S, &x, .Xchg, y, .seq_cst); + try expect(prev.b == 2); + try expect(x.b == 4); +} +``` + +### `@ptrCast` 可以改变切片长度 + +具体讨论和实现可以见PR[#22706](https://github.com/ziglang/zig/pull/22706) + +### 移除匿名结构类型,统一元组 + +此更改重新设计了匿名结构体字面量和元组的工作方式。 + +以前,一个无类型的匿名结构体字面量(例如 `const x = .{ .a = 123 }`)被赋予了一个“匿名结构体类型”,这是一种特殊的结构体,通过结构等价进行强制转换。这种机制是我们使用结果位置语义作为类型推断的主要机制之前的遗留机制。此更改将语言更改为在此处分配的类型为“普通”结构体类型。它使用一种基于 AST 节点和类型结构的等价形式,非常类似于具体化的 (`@Type`) 类型。 + +此外,元组也被简化了。“简单”元组类型和“复杂”元组类型之间的区别被消除了。所有元组,即使是那些使用 `struct { ... }` 语法显式声明的元组,也使用结构等价,并且不进行分阶段类型解析。元组的限制非常严格:它们不能有非自动布局,不能有对齐字段,不能有默认值(编译时字段除外)。元组目前没有优化布局,但这在未来可以更改。 + +此更改简化了语言,并修复了一些通过指针进行的导致非直观行为的问题强制转换。 + +### 调用约定增强并且替换 `@setAlignStack` + +Zig 允许使用 `callconv(...)` 声明函数的调用约定,其中括号中的值是类型为 `std.builtin.CallingConvention`。在之前的 Zig 版本中,这种类型是一个简单的枚举,列出了一些常见的调用约定,例如 x86 的 `.Stdcall` 和 ARM 的 `.AAPCS`。 `.C` 指的是目标的默认 C 调用约定。 + +Zig `0.14.0` 将 `CallingConvention` 实现更加详细:它现在包含了 Zig 当前支持的每个目标的每个主要调用约定。例如 `.x86_64_sysv`、`.arm_aapcs` 和 `.riscv64_interrupt`。此外,`CallingConvention` 现在是一个标记联合类型,而不是枚举,这允许在调用约定上指定选项。 + +大多数可用的调用约定都有一个 `std.builtin.CallingConvention.CommonOptions` 的有效负载,这允许在调用函数时覆盖预期的堆栈对齐: + +```zig +/// Options shared across most calling conventions. +pub const CommonOptions = struct { + /// The boundary the stack is aligned to when the function is called. + /// `null` means the default for this calling convention. + incoming_stack_alignment: ?u64 = null, +}; +``` + +这在与使用 `-mpreferred-stack-boundary` GCC 标志编译的 C 代码交互时非常有用。 + +少数调用约定具有更复杂的选项,例如: + +```zig +/// Options for x86 calling conventions which support the regparm attribute to pass some +/// arguments in registers. +pub const X86RegparmOptions = struct { + /// The boundary the stack is aligned to when the function is called. + /// `null` means the default for this calling convention. + incoming_stack_alignment: ?u64 = null, + /// The number of arguments to pass in registers before passing the remaining arguments + /// according to the calling convention. + /// Equivalent to `__attribute__((regparm(x)))` in Clang and GCC. + register_params: u2 = 0, +}; +``` + +默认的 C 调用约定不再由一个特殊标签表示。相反,CallingConvention 包含一个名为 c 的声明,其定义如下: + +```zig +/// This is an alias for the default C calling convention for this target. +/// Functions marked as `extern` or `export` are given this calling convention by default. +pub const c = builtin.target.cCallingConvention().?; +``` + +结合声明字面量,这允许编写 callconv(.c) 来指定此调用约定。 + +Zig `0.14.0` 包含名为 `Unspecified`、`C`、`Naked`、`Stdcall` 等声明,以允许现有的 `callconv` 用法继续工作,这要归功于声明字面量。这些声明已被弃用,并将在未来的 Zig 版本中移除。 + +多数调用约定都有一个 `incoming_stack_alignment` 选项,用于指定调用函数时堆栈将对齐的字节边界,这可以用于与使用低于 ABI 要求的堆栈对齐的代码进行互操作。以前,`@setAlignStack` 内置函数可以用于这种情况;然而,它的行为定义得不太明确,并且将其应用于这种情况需要了解 ABI 的预期堆栈对齐。因此,`@setAlignStack` 内置函数已被移除。相反,用户应该在他们的 `callconv` 上注释预期的堆栈对齐,允许优化器在必要时重新对齐。这也允许优化器在调用这样的函数时避免不必要的堆栈重新对齐。为了方便起见,`CallingConvention` 有一个 `withStackAlign` 函数,可以用来改变传入的堆栈对齐。 + +迁移很简单: + +```zig +// This function will be called by C code which uses a 4-byte aligned stack. +export fn foo() void { + // I know that my target's ABI expects a 16-byte aligned stack. + @setAlignStack(16); + // ... +} +``` + +⬇️ + +```zig +// This function will be called by C code which uses a 4-byte aligned stack. +// We simply specify that on the `callconv`. +export fn foo() callconv(.withStackAlign(.c, 4)) void { + // ... +} +``` + +### 重命名 `std.builtin.Type Fields` + +在大多数情况下,Zig 的标准库遵循命名约定。Zig `0.14.0` 更新了 `std.builtin.Type` 标记联合的字段,使其遵循这些约定,将它们小写: + +```zig +pub const Type = union(enum) { + type: void, + void: void, + bool: void, + noreturn: void, + int: Int, + float: Float, + pointer: Pointer, + array: Array, + @"struct": Struct, + comptime_float: void, + comptime_int: void, + undefined: void, + null: void, + optional: Optional, + error_union: ErrorUnion, + error_set: ErrorSet, + @"enum": Enum, + @"union": Union, + @"fn": Fn, + @"opaque": Opaque, + frame: Frame, + @"anyframe": AnyFrame, + vector: Vector, + enum_literal: void, + // ... +}; +``` + +请注意,这需要对 `@"struct"`、`@"union"`、`@"enum"`、`@"opaque"` 和 `@"anyframe"` 使用“带引号的标识符”语法,因为这些标识符也是关键字。 + +此更改影响广泛,但迁移很简单: + +```zig +test "switch on type info" { + const x = switch (@typeInfo(u8)) { + .Int => 0, + .ComptimeInt => 1, + .Struct => 2, + else => 3, + }; + try std.testing.expect(0, x); +} +test "reify type" { + const U8 = @Type(.{ .Int = .{ + .signedness = .unsigned, + .bits = 8, + } }); + const S = @Type(.{ .Struct = .{ + .layout = .auto, + .fields = &.{}, + .decls = &.{}, + .is_tuple = false, + } }); + try std.testing.expect(U8 == u8); + try std.testing.expect(@typeInfo(S) == .Struct); +} +const std = @import("std"); +``` + +⬇️ + +```zig +⬇️ + +test "switch on type info" { + const x = switch (@typeInfo(u8)) { + .int => 0, + .comptime_int => 1, + .@"struct" => 2, + else => 3, + }; + try std.testing.expect(0, x); +} +test "reify type" { + const U8 = @Type(.{ .int = .{ + .signedness = .unsigned, + .bits = 8, + } }); + const S = @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = &.{}, + .decls = &.{}, + .is_tuple = false, + } }); + try std.testing.expect(U8 == u8); + try std.testing.expect(@typeInfo(S) == .@"struct"); +} +const std = @import("std"); +``` + +### 重命名 `std.builtin.Type.Pointer.Size` 的字段 + +`std.builtin.Type.Pointer.Size` 枚举的字段已被重命名为小写,就像 `std.builtin.Type` 的字段一样。同样,这是一个 break change,但可以非常容易地迁移: + +```zig +test "pointer type info" { + comptime assert(@typeInfo(*u8).pointer.size == .One); +} +test "reify pointer" { + comptime assert(@Type(.{ .pointer = .{ + .size = .One, + .is_const = false, + .is_volatile = false, + .alignment = 0, + .address_space = .generic, + .child = u8, + .is_allowzero = false, + .sentinel_ptr = null, + } }) == *u8); +} +const assert = @import("std").debug.assert; +``` + +⬇️ + +```zig +⬇️ + +test "pointer type info" { + comptime assert(@typeInfo(*u8).pointer.size == .one); +} +test "reify pointer" { + comptime assert(@Type(.{ .pointer = .{ + .size = .one, + .is_const = false, + .is_volatile = false, + .alignment = 0, + .address_space = .generic, + .child = u8, + .is_allowzero = false, + .sentinel_ptr = null, + } }) == *u8); +} +const assert = @import("std").debug.assert; +``` + +### 简化在 `std.builtin.Type` 中使用的 `?*const anyopaque` + +`std.builtin.Type.StructField` 上的 `default_value` 字段,以及 `std.builtin.Type.Array` 和 `std.builtin.Type.Pointer` 上的 `sentinel` 字段,必须使用 `?*const anyopaque`,因为 Zig 不提供让结构体的类型依赖于字段值的方法。这倒无所谓;然而,有时它并不特别符合人体工程学。 + +Zig `0.14.0` 分别将这些字段重命名为 `default_value_ptr` 和 `sentinel_ptr`,并添加了辅助方法 `defaultValue()` 和 `sentinel()`,以可选的方式加载具有正确类型的值。 + +```zig +test "get pointer sentinel" { + const T = [:0]const u8; + const ptr = @typeInfo(T).pointer; + const s = @as(*const ptr.child, @ptrCast(@alignCast(ptr.sentinel.?))).*; + comptime assert(s == 0); +} +test "reify array" { + comptime assert(@Type(.{ .array = .{ .len = 1, .child = u8, .sentinel = null } }) == [1]u8); + comptime assert(@Type(.{ .array = .{ .len = 1, .child = u8, .sentinel = &@as(u8, 0) } }) == [1:0]u8); +} +const assert = @import("std").debug.assert; +``` + +⬇️ + +```zig +test "get pointer sentinel" { + const T = [:0]const u8; + const ptr = @typeInfo(T).pointer; + const s = ptr.sentinel().?; + comptime assert(s == 0); +} +test "reify array" { + comptime assert(@Type(.{ .array = .{ .len = 1, .child = u8, .sentinel_ptr = null } }) == [1]u8); + comptime assert(@Type(.{ .array = .{ .len = 1, .child = u8, .sentinel_ptr = &@as(u8, 0) } }) == [1:0]u8); +} +const assert = @import("std").debug.assert; +``` + +### 不允许非标量哨兵类型 + +哨兵值现在禁止使用复合类型。换句话说,只允许支持 `==` 操作符的类型。 + +```zig +export fn foo() void { + const S = struct { a: u32 }; + var arr = [_]S{ .{ .a = 1 }, .{ .a = 2 } }; + const s = arr[0..1 :.{ .a = 1 }]; + _ = s; +} +``` + +以上代码会触发以下错误: + +```sh +$ zig test non_scalar_sentinel.zig +src/download/0.14.0/release-notes/non_scalar_sentinel.zig:4:26: error: non-scalar sentinel type 'non_scalar_sentinel.foo.S' + const s = arr[0..1 :.{ .a = 1 }]; + ~^~~~~~~~~~ +src/download/0.14.0/release-notes/non_scalar_sentinel.zig:2:15: note: struct declared here + const S = struct { a: u32 }; + ^~~~~~~~~~~~~~~~~ +referenced by: + foo: src/download/0.14.0/release-notes/non_scalar_sentinel.zig:1:1 +``` + +### 新增内置函数 `@FieldType` + +Zig `0.14.0` 引入了 `@FieldType` 内置函数。它的作用与 `std.meta.FieldType` 函数相同:给定一个类型和其字段名,返回该字段的类型。 + +```zig +const assert = @import("std").debug.assert; +test "struct @FieldType" { + const S = struct { a: u32, b: f64 }; + comptime assert(@FieldType(S, "a") == u32); + comptime assert(@FieldType(S, "b") == f64); +} +test "union @FieldType" { + const U = union { a: u32, b: f64 }; + comptime assert(@FieldType(U, "a") == u32); + comptime assert(@FieldType(U, "b") == f64); +} +test "tagged union @FieldType" { + const U = union(enum) { a: u32, b: f64 }; + comptime assert(@FieldType(U, "a") == u32); + comptime assert(@FieldType(U, "b") == f64); +} +``` + +### `@src` 增加了 `Module` 字段 + +`std.builtin.SourceLocation`: + +```zig +pub const SourceLocation = struct { + /// The name chosen when compiling. Not a file path. + module: [:0]const u8, + /// Relative to the root directory of its module. + file: [:0]const u8, + fn_name: [:0]const u8, + line: u32, + column: u32, +}; +``` + +新增字段 `module`。 + +### `@memcpy` 规则调整 + +- `@memcpy` 的语言规范定义已更改,源和目标元素类型必须是内存可强制转换的,允许所有此类调用成为原始复制操作,而不实际应用任何强制转换。 +- 为编译时 `@memcpy` 实现别名检查;如果参数别名,现在将发出编译错误。 +- 通过一次加载和存储整个数组来实现更高效的编译时 `@memcpy`,类似于 `@memset` 的实现方式。 + +这是一个break change,因为虽然旧的强制转换行为在运行时触发了“未实现”的编译错误,但它确实在编译时起作用。 + +### 不允许不安全的内存强制转换 + +具体见 PR [#22243](https://github.com/ziglang/zig/pull/22243)。 + +### `callconv`、`align`、`addrspace`、`linksection` 不能引用函数参数 + +具体见 PR [#22264](https://github.com/ziglang/zig/pull/22264)。 + +### 函数调用的分支配额规则已调整 + +具体见 PR [#22414](https://github.com/ziglang/zig/pull/22414)。 \ No newline at end of file From 5bac814dbe0881249b2deade4de38dcf81c82343 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sat, 15 Mar 2025 19:06:37 +0800 Subject: [PATCH 07/12] =?UTF-8?q?=E5=B0=8F=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- course/update/upgrade-0.14.0.md | 51 +++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/course/update/upgrade-0.14.0.md b/course/update/upgrade-0.14.0.md index aad6072a..e45ba25c 100644 --- a/course/update/upgrade-0.14.0.md +++ b/course/update/upgrade-0.14.0.md @@ -123,10 +123,10 @@ pub const BufferB = extern struct { len: usize, pub const empty: BufferB = .{ .ptr = null, .len = 0 }; }; -// 以一种更简单的方式创建一个新的空buffer +// 以一种更简单的方式创建一个新的空 buffer var empty_buf_b: BufferB = .empty; -// 不会再出现莫名其妙的字段覆盖! -// 如果我们要指定值,那么就需要都指定值,这会使错误更容易暴露出来: +// 不会再出现莫名其妙的字段覆盖! +// 如果我们要指定值,那么就需要都指定值,这会使错误更容易暴露出来: var bad_buf_b: BufferB = .{ .ptr = null, .len = 10 }; ``` @@ -394,18 +394,18 @@ fn foo(comptime x: u8) void { 例如: -| thread-1 | thread-2 | -|-------------------------|-------------------------| -| store X // A | store Y // C | -| fence(seq_cst) // F1 | fence(seq_cst) // F2 | -| load Y // B | load X // D | +| thread-1 | thread-2 | +| -------------------- | -------------------- | +| store X // A | store Y // C | +| fence(seq_cst) // F1 | fence(seq_cst) // F2 | +| load Y // B | load X // D | 目标是确保要么 `load X (D)` 看到 `store X (A)`,要么 `load Y (B)` 看到 `store Y (C)`。这一对顺序一致的栅栏通过两个不变[1](https://en.cppreference.com/w/cpp/atomic/memory_order#Strongly_happens-before:~:text=for%20every%20pair%20of%20atomic%20operations%20A%20and%20B%20on%20an%20object%20M%2C%20where%20A%20is%20coherence%2Dordered%2Dbefore%20B%3A)[2](https://en.cppreference.com/w/cpp/atomic/memory_order#Strongly_happens-before:~:text=if%20a%20memory_order_seq_cst%20fence%20X%20happens%2Dbefore%20A%2C%20and%20B%20happens%2Dbefore%20a%20memory_order_seq_cst%20fence%20Y%2C%20then%20X%20precedes%20Y%20in%20S.)来保证这一点。 现在 `@fence` 已被删除,还有其他方法可以实现这种关系: - 将所有相关的存储和加载(A、B、C 和 D)设为 `SeqCst`,将它们全部包含在总顺序中。 -- 将存储操作(A/C)设为 `Acquire`,并将其匹配的加载操作(D/B)设为 `Release`。从语义上讲,这意味着将它们升级为读-修改-写操作,这可以实现这样的排序。加载操作可以替换为非变异的 RMW 操作,即 `fetchAdd(0)` 或 `fetchOr(0)`。 +- 将存储操作(A/C)设为 `Acquire`,并将其匹配的加载操作(D/B)设为 `Release`。从语义上讲,这意味着将它们升级为读 - 修改 - 写操作,这可以实现这样的排序。加载操作可以替换为非变异的 RMW 操作,即 `fetchAdd(0)` 或 `fetchOr(0)`。 像 LLVM 这样的优化器可能会在内部将其简化为 `@fence(.seq_cst)` + `load`。 @@ -453,17 +453,17 @@ if (counter.rc.fetchSub(1, .release) == 1) { 理想情况下,隐式函数应该对用户可访问,他们可以简单地在源代码中增加排序。但如果这不可能,最后的手段是引入一个原子变量来模拟栅栏的屏障。例如: -| thread-1 | thread-2 | -|-----------------------------|-----------------------------| -| queue.push() | e = signal.listen() | -| fence(.seq_cst) | fence(.seq_cst) | -| signal.notify() | if queue.empty(): e.wait() | +| thread-1 | thread-2 | +| --------------- | -------------------------- | +| queue.push() | e = signal.listen() | +| fence(.seq_cst) | fence(.seq_cst) | +| signal.notify() | if queue.empty(): e.wait() | -| thread-1 | thread-2 | -|-----------------------------|-----------------------------| -| queue.push() | e = signal.listen() | -| fetchAdd(0, .seq_cst) | fetchAdd(0, .seq_cst) | -| signal.notify() | if queue.empty(): e.wait() | +| thread-1 | thread-2 | +| --------------------- | -------------------------- | +| queue.push() | e = signal.listen() | +| fetchAdd(0, .seq_cst) | fetchAdd(0, .seq_cst) | +| signal.notify() | if queue.empty(): e.wait() | ### `packed` 结构体相等性 @@ -507,7 +507,7 @@ test "packed struct atomics" { ### `@ptrCast` 可以改变切片长度 -具体讨论和实现可以见PR[#22706](https://github.com/ziglang/zig/pull/22706) +具体讨论和实现可以见 PR[#22706](https://github.com/ziglang/zig/pull/22706) ### 移除匿名结构类型,统一元组 @@ -841,7 +841,7 @@ pub const SourceLocation = struct { - 为编译时 `@memcpy` 实现别名检查;如果参数别名,现在将发出编译错误。 - 通过一次加载和存储整个数组来实现更高效的编译时 `@memcpy`,类似于 `@memset` 的实现方式。 -这是一个break change,因为虽然旧的强制转换行为在运行时触发了“未实现”的编译错误,但它确实在编译时起作用。 +这是一个 break change,因为虽然旧的强制转换行为在运行时触发了“未实现”的编译错误,但它确实在编译时起作用。 ### 不允许不安全的内存强制转换 @@ -853,4 +853,11 @@ pub const SourceLocation = struct { ### 函数调用的分支配额规则已调整 -具体见 PR [#22414](https://github.com/ziglang/zig/pull/22414)。 \ No newline at end of file +具体见 PR [#22414](https://github.com/ziglang/zig/pull/22414)。 + +## 标准库 + +未分类的更改: + +- mem:在 `byteSwapAllFields` 中处理 `Float` 和 `Bool` 情况 +- fmt:从二进制中移除占位符 From a3f3acd842943a0f02a96e4e218564803ee9e357 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sat, 15 Mar 2025 21:38:33 +0800 Subject: [PATCH 08/12] =?UTF-8?q?`0.14.0`=20=E8=AF=AD=E8=A8=80=E5=8D=87?= =?UTF-8?q?=E7=BA=A7=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- course/update/upgrade-0.14.0.md | 558 ++++++++++++++++++++++++++++++++ 1 file changed, 558 insertions(+) diff --git a/course/update/upgrade-0.14.0.md b/course/update/upgrade-0.14.0.md index e45ba25c..f897bca6 100644 --- a/course/update/upgrade-0.14.0.md +++ b/course/update/upgrade-0.14.0.md @@ -861,3 +861,561 @@ pub const SourceLocation = struct { - mem:在 `byteSwapAllFields` 中处理 `Float` 和 `Bool` 情况 - fmt:从二进制中移除占位符 + +### DebugAllocator + +`GeneralPurposeAllocator` 依赖于编译时已知的页面大小,因此必须重写。 + +现在它被重写以减少活动映射,以获得更好的性能,并重命名为 `DebugAllocator`。 + +性能数据展示,这是在重写前后使用调试版 Zig 编译器运行 ast-check 的结果: + +**Benchmark 1 (3 runs)**: `master/bin/zig ast-check ../lib/compiler_rt/udivmodti4_test.zig` + +| Measurement | Mean ± σ | Min … Max | Outliers | Delta | +| ---------------- | --------------- | --------------- | -------- | ----- | +| Wall Time | 22.8s ± 184ms | 22.6s … 22.9s | 0 (0%) | 0% | +| Peak RSS | 58.6MB ± 77.5KB | 58.5MB … 58.6MB | 0 (0%) | 0% | +| CPU Cycles | 38.1G ± 84.7M | 38.0G … 38.2G | 0 (0%) | 0% | +| Instructions | 27.7G ± 16.6K | 27.7G … 27.7G | 0 (0%) | 0% | +| Cache References | 1.08G ± 4.40M | 1.07G … 1.08G | 0 (0%) | 0% | +| Cache Misses | 7.54M ± 1.39M | 6.51M … 9.12M | 0 (0%) | 0% | +| Branch Misses | 165M ± 454K | 165M … 166M | 0 (0%) | 0% | + +**Benchmark 2 (3 runs)**: `branch/bin/zig ast-check ../lib/compiler_rt/udivmodti4_test.zig` + +| Measurement | Mean ± σ | Min … Max | Outliers | Delta | +| ---------------- | -------------- | --------------- | -------- | ----------------- | +| Wall Time | 20.5s ± 95.8ms | 20.4s … 20.6s | 0 (0%) | ⚡- 10.1% ± 1.5% | +| Peak RSS | 54.9MB ± 303KB | 54.6MB … 55.1MB | 0 (0%) | ⚡- 6.2% ± 0.9% | +| CPU Cycles | 34.8G ± 85.2M | 34.7G … 34.9G | 0 (0%) | ⚡- 8.6% ± 0.5% | +| Instructions | 25.2G ± 2.21M | 25.2G … 25.2G | 0 (0%) | ⚡- 8.8% ± 0.0% | +| Cache References | 1.02G ± 195M | 902M … 1.24G | 0 (0%) | - 5.8% ± 29.0% | +| Cache Misses | 4.57M ± 934K | 3.93M … 5.64M | 0 (0%) | ⚡- 39.4% ± 35.6% | +| Branch Misses | 142M ± 183K | 142M … 142M | 0 (0%) | ⚡- 14.1% ± 0.5% | + +### SmpAllocator + +一个为 `ReleaseFast` 优化模式设计的分配器,启用了多线程。 + +这个分配器是一个单例;它使用全局状态,并且整个进程中只应实例化一个。 + +这是一个“sweet spot”——实现大约 200 行代码,但性能与 glibc 相媲美。例如,以下是使用 `glibc malloc` 与 `SmpAllocator` 构建 Zig 自身的比较: + +**Benchmark 1 (3 runs)**: `glibc/bin/zig build -Dno-lib -p trash` + +| Measurement | Mean ± σ | Min … Max | Outliers | Delta | +|--------------------|------------------|-------------------|----------|-------| +| Wall Time | 12.2s ± 99.4ms | 12.1s … 12.3s | 0 (0%) | 0% | +| Peak RSS | 975MB ± 21.7MB | 951MB … 993MB | 0 (0%) | 0% | +| CPU Cycles | 88.7G ± 68.3M | 88.7G … 88.8G | 0 (0%) | 0% | +| Instructions | 188G ± 1.40M | 188G … 188G | 0 (0%) | 0% | +| Cache References | 5.88G ± 33.2M | 5.84G … 5.90G | 0 (0%) | 0% | +| Cache Misses | 383M ± 2.26M | 381M … 385M | 0 (0%) | 0% | +| Branch Misses | 368M ± 1.77M | 366M … 369M | 0 (0%) | 0% | + +**Benchmark 2 (3 runs)**: `SmpAllocator/fast/bin/zig build -Dno-lib -p trash` + +| Measurement | Mean ± σ | Min … Max | Outliers | Delta | +|--------------------|------------------|-------------------|----------|----------------------| +| Wall Time | 12.2s ± 49.0ms | 12.2s … 12.3s | 0 (0%) | + 0.0% ± 1.5% | +| Peak RSS | 953MB ± 3.47MB | 950MB … 957MB | 0 (0%) | - 2.2% ± 3.6% | +| CPU Cycles | 88.4G ± 165M | 88.2G … 88.6G | 0 (0%) | - 0.4% ± 0.3% | +| Instructions | 181G ± 6.31M | 181G … 181G | 0 (0%) | ⚡- 3.9% ± 0.0% | +| Cache References | 5.48G ± 17.5M | 5.46G … 5.50G | 0 (0%) | ⚡- 6.9% ± 1.0% | +| Cache Misses | 386M ± 1.85M | 384M … 388M | 0 (0%) | + 0.6% ± 1.2% | +| Branch Misses | 377M ± 899K | 377M … 378M | 0 (0%) | 💩+ 2.6% ± 0.9% | + +设计思路: + +每个线程都有一个单独的空闲列表,但是,当线程退出时,数据必须是可恢复的。我们不会直接知道线程何时退出,因此有时一个线程必须尝试回收另一个线程的资源。 + +超过一定大小的分配直接进行内存映射,不存储分配元数据。这是可行的,因为这个分配器实现拒绝 resize(将从小的 buffer 移动到大的 buffer或反过来的行为)。 + +每个分配器操作从线程局部变量检查线程标识符,以确定访问全局状态中的哪个元数据,并尝试获取其锁。这通常会在没有争用的情况下成功,除非另一个线程被分配了相同的 ID。在这种争用的情况下,线程会移动到下一个线程元数据槽,并重复尝试获取锁的过程。 + +通过将线程局部元数据数组限制为与 CPU 数量相同,确保随着线程的创建和销毁,它们循环通过整个空闲列表集。 + +要使用这个新的 `allocator`,在你的主函数中放置类似以下内容的代码: + +```zig +var debug_allocator: std.heap.DebugAllocator(.{}) = .init; + +pub fn main() !void { + const gpa, const is_debug = gpa: { + if (native_os == .wasi) break :gpa .{ std.heap.wasm_allocator, false }; + break :gpa switch (builtin.mode) { + .Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true }, + .ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false }, + }; + }; + defer if (is_debug) { + _ = debug_allocator.deinit(); + }; +} +``` + +更多的信息可以看开发日志 [No-Libc Zig Now Outperforms Glibc Zig](https://ziglang.org/devlog/2025/#2025-02-07)。 + +### Allocator API 变动 (remap) + +此版本在 `std.mem.Allocator.VTable` 中引入了一个新函数 `remap`。 + +以下为文档注释中的关键部分: + +> 尝试扩展或缩小内存,允许重新定位。 +> +> 非空返回值表示调整大小成功。 +> +> 分配可能具有相同的地址,或者可能已重新定位。 +> +> 在任何一种情况下,分配现在的大小都是 new_len。 +> +> 空返回值表示调整大小相当于分配新内存,从旧内存复制字节,然后释放旧内存。 +> +> 在这种情况下,调用者执行复制操作更为高效。 + +函数原型: + +`remap: *const fn (*anyopaque, memory: []u8, alignment: Alignment, new_len: usize, return_address: usize) ?[*]u8,` + +所有 `Allocator.VTable` 函数现在接受 `std.mem.Alignment` 类型而不是 `u8`。具体数值相同,但现在有类型安全和附加到类型的便捷方法。 + +`resize` 和 `remap` 各有其用途。例如,`resize` 对于 `std.heap.ArenaAllocator` 是必要的,因为它不能重新定位其分配。同时,当容量增加时,`remap` 适用于 `std.ArrayList`。 + +关于 `remap` 需要注意,除非可以在不执行分配器内部 `memcpy` 的情况下实现 `remap`,否则 `Allocator` 实现 `remap` 通常应与 `resize` 行为相同。 + +例如,此版本在支持的情况下引入了对调用 `mremap` 的支持,在这种情况下,操作系统重新映射页面,避免了用户空间中昂贵的 `memcpy`。Zig 程序员现在可以期望在使用 `std.heap.page_allocator` 以及将其用作例如 `std.heap.ArenaAllocator` 或 `std.heap.GeneralPurposeAllocator` 的后备分配器时发生这种情况。 + +另外: + +- `std.heap.page_allocator` 现在支持大于页面大小的对齐,这在重写 `DebugAllocator` 时是需要的。 +- 删除 `std.heap.WasmPageAllocator`,改用 `std.heap.WasmAllocator`。 +- 删除 `std.heap.LoggingAllocator`,它不属于 `std`。 +- 删除 `std.heap.HeapAllocator` - 这是仅限 Windows 的,并且依赖于 `kernel32`。 + +### Zon 解析和序列化 + +`std.zon.parse` 提供了在运行时将 **ZON** 解析为 **Zig** 结构体的功能: + +- `std.zon.parse.fromSlice` +- `std.zon.parse.fromZoir` +- `std.zon.parse.fromZoirNode` +- `std.zon.parse.free` + +典型用例将使用 `std.zon.parse.fromSlice`,如果类型需要分配,则需要使用 `std.zon.parse.free`。 + +对于具有与 Zig 结构体不完全对应的模式的 ZON 值,可以使用 `std.zig.ZonGen` 生成一个可以根据需要解释的树结构(`std.Zoir`)。 + +有关在编译时导入 ZON,请参见 `Import ZON`。 + +`std.zon.stringify` 提供了在运行时序列化 ZON 的功能: + +- `std.zon.stringify.serialize` +- `std.zon.stringify.serializeMaxDepth` +- `std.zon.stringify.serializeArbitraryDepth` +- `std.zon.stringify.serializer` + +示例将使用 `serialize` 和其他函数。 + +`std.zon.stringify.serializer` 返回一个更细粒度的接口。它可以用于逐块序列化值,例如对值的不同部分应用不同的配置,或者以与内存中布局不同的形式序列化值。 + +### 运行时页面大小 + +编译时已知的 `std.mem.page_size` 被移除,因为页面大小实际上是在运行时已知的(对此表示抱歉),并用 `std.heap.page_size_min` 和 `std.heap.page_size_max` 替代,以用于可能页面大小的编译时已知边界。在指针对齐属性中使用页面大小的地方,例如在 `mmap` 中,已迁移到 `std.heap.page_size_min`。 + +在必须使用页面大小的地方,`std.heap.pageSize()` 提供解决方案。如果可能,它将返回一个编译时已知的值,否则将在运行时查询操作系统,并记忆化结果(原子地)。它还具有 `std.options` 集成,因此应用程序维护者可以覆盖此行为。 + +值得注意的是,这修复了对运行在苹果新硬件上的 **Linux** 的支持,例如 Asahi Linux。 + +### Panic 接口 + +具体改动可以参考该 PR [#22594](https://github.com/ziglang/zig/pull/22594)。 + +### 传输层安全(std.crypto.tls) + +具体信息可以见 PR [#21872](https://github.com/ziglang/zig/pull/21872)。 + +### `process.Child.collectOutput` API 变动 + +升级指南: + +```zig +var stdout = std.ArrayList(u8).init(allocator); +defer stdout.deinit(); +var stderr = std.ArrayList(u8).init(allocator); +defer stderr.deinit(); + +try child.collectOutput(&stdout, &stderr, max_output_bytes); +``` + +⬇️ + +```zig +var stdout: std.ArrayListUnmanaged(u8) = .empty; +defer stdout.deinit(allocator); +var stderr: std.ArrayListUnmanaged(u8) = .empty; +defer stderr.deinit(allocator); + +try child.collectOutput(allocator, &stdout, &stderr, max_output_bytes); +``` + +在此之前,`collectOutput` 包含一个检查,以确保 `stdout.allocator` 与 `stderr.allocator` 相同,这是由于其内部实现的必要性。然而,比较 `Allocator` 接口的 `ptr` 字段可能会导致非法行为,因为在分配器的实现没有任何关联状态的情况下(如 `page_allocator`、`c_allocator` 等),`Allocator.ptr` 被设置为未定义。 + +通过此更改,`collectOutput` 中的不安全的 `Allocator.ptr` 比较已被清除(这是 Zig 代码库中唯一出现的此类比较)。此外,`Allocator` 和 `Random` 接口的 `ptr` 字段的文档已更新,标注了对这些字段的任何比较都可能导致非法行为。未来,这种比较将被检测为非法行为。 + +### LLVM 构建器 API + +Zig 是为数不多的直接生成 LLVM 位代码的编译器之一,而不是依赖于具有不稳定 API 且非常庞大的 libLLVM。这是我们努力完全消除 Zig 中 LLVM 依赖的一部分([#16270](https://github.com/ziglang/zig/issues/16270))。Roc 项目最近[决定](https://gist.github.com/rtfeldman/77fb430ee57b42f5f2ca973a3992532f)用 Zig 重写他们的编译器,部分原因是能够重用 Zig 的 LLVM 位代码构建器。为了使这一过程更加容易,我们决定将构建器 API 移动到 `std.zig.llvm` 以供第三方项目使用。请注意,与 `std.zig` 命名空间中的内容一样,这是 Zig 编译器的实现细节,不一定遵循与标准库其他部分相同的 API 稳定性和弃用规范。 + +### 拥抱 “Unmanaged” 风格的容器 + +`std.ArrayHashMap` 现在已被弃用,并别名到了 `std.ArrayHashMapWithAllocator`。 + +要迁移代码,请切换到 `ArrayHashMapUnmanaged`,这将需要更新函数调用以向需要分配器的方法传递一个分配器。在 Zig `0.14.0` 发布后,`std.ArrayHashMapWithAllocator` 将被移除,`std.ArrayHashMapUnmanaged` 将成为 `ArrayHashMap` 的弃用别名。在 Zig `0.15.0` 发布后,弃用的别名 `ArrayHashMapUnmanaged` 将被移除。 + +这一举措来自于资深 Zig 用户的一致意见,他们已经趋向于使用 “Unmanaged” 容器。它们作为更好的构建块,避免了冗余存储相同的数据,并且分配器参数的存在 / 不存在与保留容量 / 保留插入模式很好地契合。 + +其他 “Unmanaged” 容器的派生也被弃用,例如 `std.ArrayList`。 + +```zig +var list = std.ArrayList(i32).init(gpa); +defer list.deinit(); +try list.append(1234); +try list.ensureUnusedCapacity(1); +list.appendAssumeCapacity(5678); +``` + +⬇️ + +```zig +const ArrayList = std.ArrayListUnmanaged; +var list: std.ArrayList(i32) = .empty; +defer list.deinit(gpa); +try list.append(gpa, 1234); +try list.ensureUnusedCapacity(gpa, 1); +list.appendAssumeCapacity(5678); +``` + +### 弃用列表 + +以下弃用的别名现在会导致编译错误: + +- `std.fs.MAX_PATH_BYTES`(重命名为 `std.fs.max_path_bytes`) +- `std.mem.tokenize`(拆分为 `tokenizeAny`、`tokenizeSequence`、`tokenizeScalar`) +- `std.mem.split`(拆分为 `splitSequence`、`splitAny`、`splitScalar`) +- `std.mem.splitBackwards`(拆分为 `splitBackwardsSequence`、`splitBackwardsAny`、`splitBackwardsScalar`) +- `std.unicode` +- `utf16leToUtf8Alloc`、`utf16leToUtf8AllocZ`、`utf16leToUtf8`、`fmtUtf16le`(全部重命名为首字母大写的 `Le`) +- `utf8ToUtf16LeWithNull`(重命名为 `utf8ToUtf16LeAllocZ`) +- `std.zig.CrossTarget`(移动到 `std.Target.Query`) +- `std.fs.Dir: Rename OpenDirOptions to OpenOptions` +- `std.crypto.tls.max_cipertext_inner_record_len` 重命名为 `std.crypto.tls.max_ciphertext_inner_record_len` + +被删除的顶级 `std` 命名空间: + +- `std.rand`(重命名为 `std.Random`) +- `std.TailQueue`(重命名为 `std.DoublyLinkedList`) +- `std.ChildProcess`(重命名/移动到 `std.process.Child`) + +更多弃用: + +- `std.posix.iovec`: 使用 `.base` 和 `.len` 代替 `.iov_base` 和 `.iov_len` +- `LockViolation` 被添加到 `std.posix.ReadError`。如果 `std.os.windows.ReadFile` 遇到 `ERROR_LOCK_VIOLATION`,将发生此错误。 +- 在所有容器类型中,`popOrNull` 重命名为 `pop` + +### `std.c` 重组 + +现在它由以下主要部分组成: + +1. 所有操作系统共享的声明。 +2. 具有相同名称但根据操作系统具有不同类型签名的声明。然而,多个操作系统通常共享相同的类型签名。 +3. 特定于单个操作系统的声明。 + - 这些声明每行导入一个,以便可以看到它们的来源,并在操作系统特定文件内通过 `comptime` 块保护,以防止访问错误的声明。 +4. 底部有一个名为 `private` 的命名空间,它是一个声明包,用于上面的逻辑选择和使用。 + +通过将不存在的符号的约定从 `@compileError` 更改为使类型为 `void` 和函数为 `{}` 来解决 [#19352](https://github.com/ziglang/zig/issues/19352) 问题,从而可以更新 `@hasDecl` 以使用 `@TypeOf(f) != void` 或 `T != void`。令人高兴的是,这最终删除了一些重复的逻辑并更新了一些过时的功能检测检查。 + +一些类型已被修改以获得命名空间、类型安全并符合字段命名约定。这是 break change。 + +通过此更改,标准库中最后一个 `usingnamespace` 的使用被消除。 + +### 二分查找 + +具体见此 PR [#20927](https://github.com/ziglang/zig/pull/20927)。 + +### `std.hash_map` 增加 `rehash` 方法 + +无序哈希表目前有一个严重缺陷:[删除操作会导致 `HashMaps` 变慢](https://github.com/ziglang/zig/issues/17851)。 + +未来,哈希表将进行调整以消除这一缺陷,届时该方法将被直接删除。 + +请注意,array hash maps 没有这个缺陷。 + +## 构建系统 + +未分类的更改: + +- 报告缺少 `addConfigHeader` 值的错误 +- 修复 `WriteFile` 和 `addCSourceFiles` 未添加 `LazyPath` 依赖项的问题 +- [破坏性更改] `Compile.installHeader` 现在接受 `LazyPath`。 +- [破坏性更改] `Compile.installConfigHeader` 的第二个参数已被移除,现在使用 `include_path` 的值作为其子路径,以与 `Module.addConfigHeader` 保持一致。如果想将子路径设置为不同的值,请使用 `artifact.installHeader(config_h.getOutput(), "foo.h")`。 +- [破坏性更改] `Compile.installHeadersDirectory/installHeadersDirectoryOptions` 已合并为 `Compile.installHeadersDirectory`,它接受 `LazyPath` 并允许排除/包含过滤器,就像 `InstallDir` 一样。 +- [破坏性更改] `b.addInstallHeaderFile` 现在接受 `LazyPath`。 +- [破坏性更改] [#9698](https://github.com/ziglang/zig/issues/9698) 的解决方法,即使用户为 `h_dir` 指定了覆盖,生成的 `-femit-h` 头文件现在也不会被发出。如果您绝对需要发出的头文件,现在需要执行 `install_artifact.emitted_h = artifact.getEmittedH()` 直到 `-femit-h` 被修复。 +- 添加了 `WriteFile.addCopyDirectory`,其功能与 `InstallDir` 非常相似。 +- `InstallArtifact` 已更新,以便将捆绑的头文件与工件一起安装。捆绑的头文件安装到 `h_dir` 指定的目录(默认为 `zig-out/include`)。 +- `std.Build`: 检测带有 "lib" 前缀的 `pkg-config` 名称 +- `fetch`: 添加对 SHA-256 Git 仓库的支持 +- `fetch`: 添加对 Mach-O 文件头的可执行文件检测 +- 允许在 `comptime` 之外添加 `ConfigHeader` 值 + +### 文件系统监控 + +- `--watch` 持续监控源文件修改并重新构建 +- `--debounce ` 检测到文件更改后重新构建前的延迟 + +使用构建系统对所有文件系统输入的完美控制,在完成后保持构建运行器活跃,监控最少数量的目录,以便仅重新运行图中脏的步骤。 + +默认的去抖动时间是 50ms,但它可配置。这有助于防止在源文件快速连续更改时浪费重建,例如在使用 vim 保存时,它不会进行原子重命名,而是实际上删除目标文件然后再次写入,导致短暂的无效状态,如果没有去抖动会导致构建失败(随后会成功构建,但无论如何体验到临时构建失败是令人恼火的)。 + +此功能的目的是减少开发周期中编辑和调试之间的延迟。在大型项目中,即使是缓存命中,缓存系统也必须调用 `fstat` 来处理大量文件。文件系统监控允许更高效地检测过时的管道步骤。 + +主要动机是增量编译即将到来,以便我们可以保持编译器运行并尽快响应源代码更改。在这种情况下,保持其余构建管道的最新状态也是基本要求。 + +### 新的包哈希格式 + +旧的哈希格式如下所示:`1220115ff095a3c970cc90fce115294ba67d6fbc4927472dc856abc51e2a1a9364d7` + +新的哈希格式如下所示:`mime-3.0.0-zwmL-6wgAADuFwn7gr-_DAQDGJdIim94aDIPa6qO-6GT` + +除了 200 位的 SHA-256,新哈希还包含以下附加数据: + +- 名称 +- 版本 +- 指纹的 ID 组件 +- 磁盘上的总解压大小 + +这在编译错误或文件路径中显示包哈希时提供了更好的用户体验,并提供了实现依赖树管理工具所需的数据。例如,仅通过了解整个依赖树的包哈希,现在可以知道在完成所有获取后磁盘上所需的总文件大小,以及执行版本选择,而无需进行任何获取。 + +文件大小还可以作为默认情况下是否获取懒加载包的启发式方法。 + +这些好处需要一些新的规则来管理 `build.zig.zon` 文件: + +- 名称和版本限制为 32 字节。 +- 名称必须是有效的裸 Zig 标识符。将来,这一限制可能会被取消;目前选择了保守的规则。 + +指纹是一个重要的概念: + +- 与名称一起,这代表了一个全局唯一的包标识符。该字段在包首次创建时由工具链自动初始化,然后永远不会更改。尽管生态系统是去中心化的,但这允许 Zig 明确检测一个包是否是另一个包的更新版本。 +- 当分叉一个 Zig 项目时,如果上游项目仍在维护,则应重新生成此指纹。否则,分叉是敌对的,试图控制原始项目的身份。可以通过删除该字段并运行 `zig build` 来重新生成指纹。 +- 这个 64 位整数是 32 位 ID 组件和 32 位校验和的组合。 + +指纹中的 ID 组件有以下限制: + +- `0x00000000` 保留用于旧包。 +- `0xffffffff` 保留用于表示“裸”包。 + +校验和是从名称计算的,用于保护 Zig 用户免受意外的 ID 冲突。 + +版本选择和利用指纹的相关工具尚未实现。 + +尽管仍支持旧的哈希格式,但此更改会破坏任何不遵循上述新包命名规则的包。还有一个已知的错误:不必要地获取旧包。 + +### `WriteFile` Step + +如果您使用 `WriteFile` 来更新源文件,该功能已被提取到一个单独的步骤,称为 `UpdateSourceFiles`。其他一切都保持不变,因此迁移如下所示: + +```diff +- const copy_zig_h = b.addWriteFiles(); ++ const copy_zig_h = b.addUpdateSourceFiles(); +``` + +### `RemoveDir` Step + +`RemoveDir` Step 现在接受 `LazyPath` 而不是 `[]const u8`。迁移如下所示: + +```diff +- const cleanup = b.addRemoveDirTree(tmp_path); ++ const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path }); +``` + +但是,请考虑不要在配置时选择临时路径,同时运行构建管道有点脆弱。 + +### `Fmt` Step + +这个 Step 用于打印格式检查失败的文件名。 + +### 从现有模块创建工件 + +Zig `0.14.0` 修改了创建 `Compile` Step 的构建系统 API,允许从现有的 `std.Build.Module` 对象创建它们。这使得模块图的定义更加清晰,并且这些图的组件可以更容易地重用;例如,作为另一个模块依赖项存在的模块可以轻松创建相应的测试步骤。可以通过修改对 `addExecutable`、`addTest` 等的调用来使用新的 API。不要直接将 `root_source_file`、`target` 和 `optimize` 等选项传递给这些函数,而是应该传递使用这些参数创建的 `*std.Build.Module` 的 `root_module` 字段。Zig `0.14.0` 仍然允许这些函数的旧的、已弃用的用法,但下一版本将移除它们。 + +旧 API 的用户可以通过将 `addExecutable`(等)调用的模块特定部分移动到 `createModule` 调用中,以最小的努力进行升级。例如,以下是一个简单构建脚本的更新版本: + +```zig +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "hello", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + b.installArtifact(exe); +} +const std = @import("std"); +``` + +⬇️ + +```zig +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "hello", + .root_module = b.createModule(.{ // this line was added + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), // this line was added + }); + b.installArtifact(exe); +} +const std = @import("std"); +``` + +而且,为了展示新 API 的优势,这里有一个示例构建脚本,它优雅地构建了一个包含多个模块的复杂构建图: + +```zig +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // First, we create our 3 modules. + + const foo = b.createModule(.{ + .root_source_file = b.path("src/foo.zig"), + .target = target, + .optimize = optimize, + }); + const bar = b.createModule(.{ + .root_source_file = b.path("src/bar.zig"), + .target = target, + .optimize = optimize, + }); + const qux = b.createModule(.{ + .root_source_file = b.path("src/qux.zig"), + .target = target, + .optimize = optimize, + }); + + // Next, we set up all of their dependencies. + + foo.addImport("bar", bar); + foo.addImport("qux", qux); + bar.addImport("qux", qux); + qux.addImport("bar", bar); // mutual recursion! + + // Finally, we will create all of our `Compile` steps. + // `foo` will be the root of an executable, but all 3 modules also have unit tests we want to run. + + const foo_exe = b.addExecutable(.{ + .name = "foo", + .root_module = foo, + }); + + b.installArtifact(foo_exe); + + const foo_test = b.addTest(.{ + .name = "foo", + .root_module = foo, + }); + const bar_test = b.addTest(.{ + .name = "bar", + .root_module = bar, + }); + const qux_test = b.addTest(.{ + .name = "qux", + .root_module = qux, + }); + + const test_step = b.step("test", "Run all unit tests"); + test_step.dependOn(&b.addRunArtifact(foo_test).step); + test_step.dependOn(&b.addRunArtifact(bar_test).step); + test_step.dependOn(&b.addRunArtifact(qux_test).step); +} +const std = @import("std"); +``` + +### 允许包通过名称暴露任意 LazyPaths + +在之前的 Zig 版本中,包可以暴露 artifact、`module` 和命名的 WriteFile Step。这些可以分别通过 `installArtifact`、`addModule` 和 `addNamedWriteFiles` 暴露,并可以通过 `std.Build.Dependency` 上的方法访问它们。 + +除了这些,Zig `0.14.0` 引入了包暴露任意 `LazyPaths` 的能力。依赖项通过 `std.Build.addNamedLazyPath` 暴露它们,依赖包使用 `std.Build.Dependency.namedLazyPath` 访问它们。 + +此功能的一个用例是让依赖项向其依赖包暴露一个生成的文件。例如,在以下示例中,依赖包 bar 暴露了一个生成的 Zig 文件,主包将其用作可执行文件的模块导入: + +***build.zig*** + +```zig +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const bar = b.dependency("bar", .{}); + const exe = b.addExecutable(.{ + .name = "main", + .root_source_file = b.path("main.zig"), + .target = target, + .optimize = optimize, + }); + exe.root_module.addImport("generated", bar.namedLazyPath("generated")); + b.installArtifact(exe); +} +``` + +***bar/build.zig*** + +```zig +pub fn build(b: *std.Build) { + const generator = b.addExecutable(.{ + .name = "generator", + .root_source_file = b.path("generator.zig"), + .target = b.graph.host, + .optimize = .ReleaseSafe, + }); + const run_gen = b.addRunArtifact(generator); + const generated_file = run_gen.addOutputFileArg("generated.zig"); + b.addNamedLazyPath("generated", generated_file); +} +``` + +### `addLibrary` 函数 + +作为 `addSharedLibrary` 和 `addStaticLibrary` 的替代,但可以更轻松地在 `build.zig` 中更改链接模式,例如: + +对于库来说: + +```zig +const lib = b.addLibrary(.{ + .linkage = linkage, + .name = "foo_bar", + .root_module = mod, +}); +``` + +对于调用库的包来说: + +```zig +const dep_foo_bar = b.dependency("foo_bar", .{ + .target = target, + .optimize = optimize, + .linkage = .dynamic // or leave for default static +}); + +mod.linkLibrary(dep_foor_bar.artifact("foo_bar")); +``` + From 847850cc8e20c56e2575c6b305ee06fe2caaab8a Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sat, 15 Mar 2025 21:39:00 +0800 Subject: [PATCH 09/12] =?UTF-8?q?`0.14.0`=20=E9=83=A8=E5=88=86=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- course/update/0.14.0-description.md | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/course/update/0.14.0-description.md b/course/update/0.14.0-description.md index e69de29b..46661249 100644 --- a/course/update/0.14.0-description.md +++ b/course/update/0.14.0-description.md @@ -0,0 +1,44 @@ +--- +outline: deep +comments: false +showVersion: false +--- + +# `0.14.0` + +2025/3/5,`0.14.0` 发布,历时 9 个月,有 251 位贡献者,一共进行了 3467 次提交! + +除了大量的构建系统升级、语言变更和目标支持增强之外,此版本还朝着我们的两项长期目标迈进——增量编译和更快的 x86 后端 - 都专注于 **减少编辑/编译/调试周期延迟**。 + +::: warning + +有关于各平台和架构的支持情况,本次更新并未进行更改,故在此处不再提及! + +::: + +## 编译器 + +未分类的更改: + +- `-fno-omit-frame-pointer` 现在是 x86 目标上 `ReleaseSmall` 的默认设置 +- 管道:更早地生成产生链接器输入的作业 + +### 多线程后端支持 +编译器的一些后端(例如 x86 后端)现在支持在与前端不同的线程中运行代码生成。作为一个数据点,这使得编译器在一台计算机上自我构建的速度从 12.8 秒加快到 8.57 秒。 + +### 增量编译 + +虽然此功能尚未准备好默认启用,但可以通过传递给 `zig build` 的 `-fincremental` 标志选择加入。建议与文件系统监视结合使用,因为编译器状态序列化尚未实现。 + +此功能的完整性因使用的链接器后端而异。它们中的任何一个都尚未普遍准备好使用,但与 `-fno-emit-bin` 结合使用效果很好。 + +鼓励用户创建一个仅检查编译错误的构建选项,并通过以下方式尝试增量编译: + +```zig +const no_bin = b.option(bool, "no-bin", "skip emitting binary") orelse false; +if (no_bin) { + b.getInstallStep().dependOn(&exe.step); +} else { + b.installArtifact(exe); +} +``` \ No newline at end of file From 0e2ece11863f3d632a8acba8c589839b0106933c Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sat, 15 Mar 2025 21:45:15 +0800 Subject: [PATCH 10/12] format --- course/update/0.14.0-description.md | 3 +- course/update/upgrade-0.14.0.md | 51 ++++++++++++++--------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/course/update/0.14.0-description.md b/course/update/0.14.0-description.md index 46661249..879f5bfa 100644 --- a/course/update/0.14.0-description.md +++ b/course/update/0.14.0-description.md @@ -24,6 +24,7 @@ showVersion: false - 管道:更早地生成产生链接器输入的作业 ### 多线程后端支持 + 编译器的一些后端(例如 x86 后端)现在支持在与前端不同的线程中运行代码生成。作为一个数据点,这使得编译器在一台计算机上自我构建的速度从 12.8 秒加快到 8.57 秒。 ### 增量编译 @@ -41,4 +42,4 @@ if (no_bin) { } else { b.installArtifact(exe); } -``` \ No newline at end of file +``` diff --git a/course/update/upgrade-0.14.0.md b/course/update/upgrade-0.14.0.md index f897bca6..119f6c77 100644 --- a/course/update/upgrade-0.14.0.md +++ b/course/update/upgrade-0.14.0.md @@ -904,33 +904,33 @@ pub const SourceLocation = struct { **Benchmark 1 (3 runs)**: `glibc/bin/zig build -Dno-lib -p trash` -| Measurement | Mean ± σ | Min … Max | Outliers | Delta | -|--------------------|------------------|-------------------|----------|-------| -| Wall Time | 12.2s ± 99.4ms | 12.1s … 12.3s | 0 (0%) | 0% | -| Peak RSS | 975MB ± 21.7MB | 951MB … 993MB | 0 (0%) | 0% | -| CPU Cycles | 88.7G ± 68.3M | 88.7G … 88.8G | 0 (0%) | 0% | -| Instructions | 188G ± 1.40M | 188G … 188G | 0 (0%) | 0% | -| Cache References | 5.88G ± 33.2M | 5.84G … 5.90G | 0 (0%) | 0% | -| Cache Misses | 383M ± 2.26M | 381M … 385M | 0 (0%) | 0% | -| Branch Misses | 368M ± 1.77M | 366M … 369M | 0 (0%) | 0% | +| Measurement | Mean ± σ | Min … Max | Outliers | Delta | +| ---------------- | -------------- | ------------- | -------- | ----- | +| Wall Time | 12.2s ± 99.4ms | 12.1s … 12.3s | 0 (0%) | 0% | +| Peak RSS | 975MB ± 21.7MB | 951MB … 993MB | 0 (0%) | 0% | +| CPU Cycles | 88.7G ± 68.3M | 88.7G … 88.8G | 0 (0%) | 0% | +| Instructions | 188G ± 1.40M | 188G … 188G | 0 (0%) | 0% | +| Cache References | 5.88G ± 33.2M | 5.84G … 5.90G | 0 (0%) | 0% | +| Cache Misses | 383M ± 2.26M | 381M … 385M | 0 (0%) | 0% | +| Branch Misses | 368M ± 1.77M | 366M … 369M | 0 (0%) | 0% | **Benchmark 2 (3 runs)**: `SmpAllocator/fast/bin/zig build -Dno-lib -p trash` -| Measurement | Mean ± σ | Min … Max | Outliers | Delta | -|--------------------|------------------|-------------------|----------|----------------------| -| Wall Time | 12.2s ± 49.0ms | 12.2s … 12.3s | 0 (0%) | + 0.0% ± 1.5% | -| Peak RSS | 953MB ± 3.47MB | 950MB … 957MB | 0 (0%) | - 2.2% ± 3.6% | -| CPU Cycles | 88.4G ± 165M | 88.2G … 88.6G | 0 (0%) | - 0.4% ± 0.3% | -| Instructions | 181G ± 6.31M | 181G … 181G | 0 (0%) | ⚡- 3.9% ± 0.0% | -| Cache References | 5.48G ± 17.5M | 5.46G … 5.50G | 0 (0%) | ⚡- 6.9% ± 1.0% | -| Cache Misses | 386M ± 1.85M | 384M … 388M | 0 (0%) | + 0.6% ± 1.2% | -| Branch Misses | 377M ± 899K | 377M … 378M | 0 (0%) | 💩+ 2.6% ± 0.9% | +| Measurement | Mean ± σ | Min … Max | Outliers | Delta | +| ---------------- | -------------- | ------------- | -------- | --------------- | +| Wall Time | 12.2s ± 49.0ms | 12.2s … 12.3s | 0 (0%) | + 0.0% ± 1.5% | +| Peak RSS | 953MB ± 3.47MB | 950MB … 957MB | 0 (0%) | - 2.2% ± 3.6% | +| CPU Cycles | 88.4G ± 165M | 88.2G … 88.6G | 0 (0%) | - 0.4% ± 0.3% | +| Instructions | 181G ± 6.31M | 181G … 181G | 0 (0%) | ⚡- 3.9% ± 0.0% | +| Cache References | 5.48G ± 17.5M | 5.46G … 5.50G | 0 (0%) | ⚡- 6.9% ± 1.0% | +| Cache Misses | 386M ± 1.85M | 384M … 388M | 0 (0%) | + 0.6% ± 1.2% | +| Branch Misses | 377M ± 899K | 377M … 378M | 0 (0%) | 💩+ 2.6% ± 0.9% | 设计思路: 每个线程都有一个单独的空闲列表,但是,当线程退出时,数据必须是可恢复的。我们不会直接知道线程何时退出,因此有时一个线程必须尝试回收另一个线程的资源。 -超过一定大小的分配直接进行内存映射,不存储分配元数据。这是可行的,因为这个分配器实现拒绝 resize(将从小的 buffer 移动到大的 buffer或反过来的行为)。 +超过一定大小的分配直接进行内存映射,不存储分配元数据。这是可行的,因为这个分配器实现拒绝 resize(将从小的 buffer 移动到大的 buffer 或反过来的行为)。 每个分配器操作从线程局部变量检查线程标识符,以确定访问全局状态中的哪个元数据,并尝试获取其锁。这通常会在没有争用的情况下成功,除非另一个线程被分配了相同的 ID。在这种争用的情况下,线程会移动到下一个线程元数据槽,并重复尝试获取锁的过程。 @@ -957,7 +957,7 @@ pub fn main() !void { 更多的信息可以看开发日志 [No-Libc Zig Now Outperforms Glibc Zig](https://ziglang.org/devlog/2025/#2025-02-07)。 -### Allocator API 变动 (remap) +### Allocator API 变动 (remap) 此版本在 `std.mem.Allocator.VTable` 中引入了一个新函数 `remap`。 @@ -1068,15 +1068,15 @@ try child.collectOutput(allocator, &stdout, &stderr, max_output_bytes); Zig 是为数不多的直接生成 LLVM 位代码的编译器之一,而不是依赖于具有不稳定 API 且非常庞大的 libLLVM。这是我们努力完全消除 Zig 中 LLVM 依赖的一部分([#16270](https://github.com/ziglang/zig/issues/16270))。Roc 项目最近[决定](https://gist.github.com/rtfeldman/77fb430ee57b42f5f2ca973a3992532f)用 Zig 重写他们的编译器,部分原因是能够重用 Zig 的 LLVM 位代码构建器。为了使这一过程更加容易,我们决定将构建器 API 移动到 `std.zig.llvm` 以供第三方项目使用。请注意,与 `std.zig` 命名空间中的内容一样,这是 Zig 编译器的实现细节,不一定遵循与标准库其他部分相同的 API 稳定性和弃用规范。 -### 拥抱 “Unmanaged” 风格的容器 +### 拥抱“Unmanaged”风格的容器 `std.ArrayHashMap` 现在已被弃用,并别名到了 `std.ArrayHashMapWithAllocator`。 要迁移代码,请切换到 `ArrayHashMapUnmanaged`,这将需要更新函数调用以向需要分配器的方法传递一个分配器。在 Zig `0.14.0` 发布后,`std.ArrayHashMapWithAllocator` 将被移除,`std.ArrayHashMapUnmanaged` 将成为 `ArrayHashMap` 的弃用别名。在 Zig `0.15.0` 发布后,弃用的别名 `ArrayHashMapUnmanaged` 将被移除。 -这一举措来自于资深 Zig 用户的一致意见,他们已经趋向于使用 “Unmanaged” 容器。它们作为更好的构建块,避免了冗余存储相同的数据,并且分配器参数的存在 / 不存在与保留容量 / 保留插入模式很好地契合。 +这一举措来自于资深 Zig 用户的一致意见,他们已经趋向于使用“Unmanaged”容器。它们作为更好的构建块,避免了冗余存储相同的数据,并且分配器参数的存在 / 不存在与保留容量 / 保留插入模式很好地契合。 -其他 “Unmanaged” 容器的派生也被弃用,例如 `std.ArrayList`。 +其他“Unmanaged”容器的派生也被弃用,例如 `std.ArrayList`。 ```zig var list = std.ArrayList(i32).init(gpa); @@ -1359,7 +1359,7 @@ const std = @import("std"); 此功能的一个用例是让依赖项向其依赖包暴露一个生成的文件。例如,在以下示例中,依赖包 bar 暴露了一个生成的 Zig 文件,主包将其用作可执行文件的模块导入: -***build.zig*** +**_build.zig_** ```zig pub fn build(b: *std.Build) void { @@ -1377,7 +1377,7 @@ pub fn build(b: *std.Build) void { } ``` -***bar/build.zig*** +**_bar/build.zig_** ```zig pub fn build(b: *std.Build) { @@ -1418,4 +1418,3 @@ const dep_foo_bar = b.dependency("foo_bar", .{ mod.linkLibrary(dep_foor_bar.artifact("foo_bar")); ``` - From d925e493d8cafe997522b80e17eb22d8d9fc3324 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 16 Mar 2025 15:56:33 +0800 Subject: [PATCH 11/12] =?UTF-8?q?=E6=96=87=E6=A1=A3=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- course/update/0.14.0-description.md | 369 +++++++++++++++++++++++++++- course/update/upgrade-0.14.0.md | 2 +- 2 files changed, 367 insertions(+), 4 deletions(-) diff --git a/course/update/0.14.0-description.md b/course/update/0.14.0-description.md index 879f5bfa..96324968 100644 --- a/course/update/0.14.0-description.md +++ b/course/update/0.14.0-description.md @@ -10,11 +10,120 @@ showVersion: false 除了大量的构建系统升级、语言变更和目标支持增强之外,此版本还朝着我们的两项长期目标迈进——增量编译和更快的 x86 后端 - 都专注于 **减少编辑/编译/调试周期延迟**。 -::: warning +## 目标支持 -有关于各平台和架构的支持情况,本次更新并未进行更改,故在此处不再提及! +此版本的 Zig 的一个重要特性是改进目标支持状况,Zig 可以正确交叉编译和运行的目标列表已增加很多。 +Zig 可以为其构建程序的目标见下表,[zig-bootstrap README](https://github.com/ziglang/zig-bootstrap/blob/master/README.md#supported-triples) 涵盖了 Zig 编译器本身可以轻松交叉编译以运行的目标。 -::: +针对特性目标修复的完整列表太长,不在列出,但简言之,如果过去尝试针对 `arm/thumb`、`mips/mips64`、`powerpc/powerpc64`、`riscv32/riscv64` 或 `s390x` 并遇到工具链问题、缺少标准库支持或看似无意义的崩溃,那么现在大概率 Zig `0.14.0` 可以正常工作。 + +### 目标三重变化 + +Zig 对理解的目标三重做了一些更改: + +- `arm-windows-gnu` 已被替换为 `thumb-windows-gnu`,以反映 Windows 仅支持 Thumb-2 模式的事实。 +- `armeb-windows-gnu` 和 `aarch64_be-windows-gnu` 已被移除,因为 Windows 不支持大端。 +- 添加了 `thumb[eb]-linux-musleabi[hf]` 以使用 musl libc 针对纯 Thumb 模式。 +- 添加了 `mips[el]-linux-musleabi` 以使用软浮点 ABI 和 musl libc 针对 32 位 mips。 +- `mips[el]-linux-musl` 已重命名为 `mips[el]-linux-musleabihf` 以明确它们针对硬浮点 ABI。 +- `mips64[el]-linux-musl` 已重命名为 `mips64[el]-linux-muslabi64` 以与 `mips64[el]-linux-gnuabi64` 保持一致。 +- 添加了 `mips64[el]-linux-muslabin32` 以使用 32 位指针和 musl libc 针对 64 位 mips。 +- 添加了 `powerpc-linux-musleabi` 以使用软浮点 ABI 和 musl libc 针对 32 位 powerpc。 +- `powerpc-linux-musl` 已重命名为 `powerpc-linux-musleabihf` 以明确它针对硬浮点 ABI。 +- 添加了 `x86_64-linux-muslx32` 以使用 32 位指针和 musl libc 针对 64 位 x86。 + +### 分层系统 + +Zig 对各种目标的支持水平大致分为四个层级,其中 Tier 1 是最高的。请注意,目前,即使是一些 Tier 1 目标也可能有一些禁用的测试,因为我们正在努力实现 100% 的测试通过率。 + +#### Tier 1 + +- 所有非实验性语言功能都已知可以正常工作。 +- 编译器可以在不依赖 LLVM 的情况下为此目标生成机器代码,同时在功能支持方面与 LLVM 相当。 +- 即使在交叉编译时,libc 也可用于此目标。 + +#### Tier 2 + +- 标准库的跨平台抽象具有此目标的实现。 +- 此目标具有调试信息功能,因此在断言失败和崩溃时会生成堆栈跟踪。 +- CI 机器会在每次提交到主分支时自动构建和测试此目标。 + +#### Tier 3 + +- 编译器可以通过依赖外部后端(如 LLVM)为此目标生成机器代码。 +- 链接器可以为此目标生成目标文件、库和可执行文件。 + +#### Tier 4 + +- 编译器可以通过依赖外部后端(如 LLVM)为此目标生成汇编源代码。 +- 此目标可能被 LLVM 视为实验性目标,在这种情况下,需要从源代码构建 LLVM 和 Zig 才能使用它。 + +### 支持列表 + +在下表中,🟢 表示完全支持,🔴 表示不支持,🟡 表示部分支持,例如仅支持某些子目标,或存在一些显著的已知问题。❔ 表示状态大致未知,通常是因为该目标很少被使用。将鼠标悬停在其他图标上以查看详细信息。 + +| 目标 | 层级 | 语言特性 | 标准库 | 代码生成 | 链接器 | 调试信息 | libc | CI | +| ------------------- | ---- | -------- | ------ | -------- | ------ | -------- | ---- | --- | +| x86_64-linux | 1 | 🟢 | 🟢 | 🖥️⚡ | 🟢 | 🟢 | 🟢 | 🟢 | +| aarch64[_be]-linux | 2 | 🟢 | 🟢 | 🖥️🛠️ | 🟢 | 🟢 | 🟢 | 🟢 | +| aarch64-macos | 2 | 🟢 | 🟢 | 🖥️ | 🟢 | 🟢 | 🟢 | 🟢 | +| aarch64-windows | 2 | 🟢 | 🟢 | 🖥️ | 🟢 | 🟢 | 🟢 | 🟢 | +| arm[eb]-linux | 2 | 🟢 | 🟢 | 🖥️🛠️ | 🟢 | 🟢 | 🟢 | 🟢 | +| powerpc-linux | 2 | 🟢 | 🟢 | 🖥️ | 🟡 | 🟢 | 🟢 | 🟡 | +| powerpc64-linux | 2 | 🟢 | 🟢 | 🖥️ | 🟡 | 🟢 | 🟡 | 🟡 | +| powerpc64le-linux | 2 | 🟢 | 🟢 | 🖥️ | 🟢 | 🟢 | 🟢 | 🟢 | +| wasm32-wasi | 2 | 🟢 | 🟢 | 🖥️⚡ | 🟢 | 🟢 | 🟢 | 🟢 | +| x86-linux | 2 | 🟢 | 🟢 | 🖥️ | 🟢 | 🟢 | 🟢 | 🟢 | +| x86-windows | 2 | 🟢 | 🟢 | 🖥️ | 🟢 | 🟢 | 🟢 | 🟢 | +| x86_64-macos | 2 | 🟢 | 🟢 | 🖥️ | 🟢 | 🟢 | 🟢 | 🟢 | +| x86_64-windows | 2 | 🟢 | 🟢 | 🖥️ | 🟢 | 🟢 | 🟢 | 🟢 | +| aarch64-freebsd | 3 | ❔ | ❔ | 🖥️🛠️ | 🟢 | 🟢 | 🔴 | 🔴 | +| aarch64[_be]-netbsd | 3 | ❔ | ❔ | 🖥️🛠️ | 🟢 | ❔ | 🔴 | 🔴 | +| aarch64-openbsd | 3 | ❔ | ❔ | 🖥️🛠️ | 🟢 | ❔ | 🔴 | 🔴 | +| hexagon-linux | 3 | 🟡 | 🟡 | 🖥️ | 🟢 | 🔴 | 🔴 | 🔴 | +| loongarch64-linux | 3 | 🟡 | 🟡 | 🖥️ | 🟢 | 🔴 | 🟢 | 🔴 | +| mips[el]-linux | 3 | 🟢 | 🟢 | 🖥️ | 🟢 | 🔴 | 🟢 | 🔴 | +| mips64[el]-linux | 3 | 🟢 | 🟢 | 🖥️ | 🟡 | 🔴 | 🟢 | 🔴 | +| riscv32-linux | 3 | 🟢 | 🟢 | 🖥️ | 🟢 | 🔴 | 🟢 | 🟢 | +| riscv64-linux | 3 | 🟢 | 🟢 | 🖥️🛠️ | 🟢 | 🔴 | 🟢 | 🟢 | +| s390x-linux | 3 | 🟢 | 🟢 | 🖥️ | 🟢 | 🔴 | 🟢 | 🟢 | +| sparc64-linux | 3 | ❔ | 🟢 | 🖥️🛠️ | 🟢 | ❔ | 🟢 | 🔴 | +| sparc64-solaris | 3 | ❔ | ❔ | 🖥️🛠️ | 🟢 | ❔ | 🔴 | 🔴 | +| wasm64-wasi | 3 | ❔ | ❔ | 🖥️⚡ | 🟢 | ❔ | 🔴 | 🔴 | +| x86_64-dragonfly | 3 | 🟢 | ❔ | 🖥️⚡ | 🟢 | ❔ | 🔴 | 🔴 | +| x86_64-freebsd | 3 | 🟢 | 🟢 | 🖥️⚡ | 🟢 | 🟢 | 🔴 | 🔴 | +| x86_64-illumos | 3 | 🟢 | ❔ | 🖥️⚡ | 🟢 | ❔ | 🔴 | 🔴 | +| x86_64-netbsd | 3 | 🟢 | 🟢 | 🖥️⚡ | 🟢 | 🟢 | 🔴 | 🔴 | +| x86_64-openbsd | 3 | 🟢 | 🟢 | 🖥️⚡ | 🟢 | 🟢 | 🔴 | 🔴 | +| x86_64-solaris | 3 | 🟢 | ❔ | 🖥️⚡ | 🟢 | ❔ | 🔴 | 🔴 | +| arc-linux | 4 | ❔ | 🟢 | 📄 | 🔴 | ❔ | 🟢 | 🔴 | +| csky-linux | 4 | ❔ | 🟢 | 📄 | 🔴 | ❔ | 🟢 | 🔴 | +| m68k-linux | 4 | ❔ | 🔴 | 🖥️ | 🔴 | ❔ | 🟢 | 🔴 | +| m68k-netbsd | 4 | ❔ | 🔴 | 🖥️ | 🔴 | ❔ | 🔴 | 🔴 | +| sparc-linux | 4 | ❔ | 🔴 | 🖥️ | 🔴 | ❔ | 🟢 | 🔴 | +| xtensa-linux | 4 | ❔ | 🔴 | 📄 | 🔴 | ❔ | 🔴 | 🔴 | + +### 附加平台 + +Zig 还对以下目标具有不同程度的支持,这些目标不完全适用于分层系统: + +| 目标 | 目标 | 目标 | +| ------------------------- | ----------------------------- | ---------------------------- | +| aarch64-driverkit | aarch64-ios | amdgcn-amdhsa | +| aarch64[_be]-freestanding | aarch64-tvos | arc-freestanding | +| aarch64-uefi | aarch64-visionos | arm[eb]-freestanding | +| aarch64-watchos | avr-freestanding | bpf(eb,el)-freestanding | +| csky-freestanding | hexagon-freestanding | kalimba-freestanding | +| lanai-freestanding | loongarch(32,64)-freestanding | loongarch(32,64)-uefi | +| m68k-freestanding | mips[64][el]-freestanding | msp430-freestanding | +| nvptx[64]-cuda | nvptx[64]-nvcl | powerpc[64][le]-freestanding | +| propeller-freestanding | riscv(32,64)-freestanding | riscv(32,64)-uefi | +| s390x-freestanding | sparc[64]-freestanding | spirv(32,64)-opencl | +| spirv(32,64)-vulkan | ve-freestanding | wasm(32,64)-emscripten | +| wasm(32,64)-freestanding | x86-elfiamcu | x86[_64]-freestanding | +| x86[_64]-uefi | x86_64-driverkit | x86_64-ios | +| x86_64-tvos | x86_64-visionos | x86_64-watchos | +| xcore-freestanding | xtensa-freestanding | | ## 编译器 @@ -43,3 +152,257 @@ if (no_bin) { b.installArtifact(exe); } ``` + +在进行大型重构并且希望快速获得编译错误反馈时: + +```sh +$ zig build -Dno-bin -fincremental --watch +Build Summary: 3/3 steps succeeded +install success +└─ zig build-exe zig Debug native success 14s + └─ options success +Build Summary: 3/3 steps succeeded +install success +└─ zig build-exe zig Debug native success 63ms +watching 119 directories, 1 processes +``` + +上面的例子中,对于 50 万行代码库,需要 14 秒才能生成编译错误。但是,由于我们使用了 `--watch` 和 `-fincremental` ,因此在进行编辑并保存后,仅需 63 毫秒即可对更改的代码进行重新分析。 + +那些在构建脚本中添加 `-Dno-bin` 选项的用户也可以享受类似的工作流程。 + +未来,这也将能够生成一个完全可用的二进制文件,可以像平常一样进行测试和调试。 + +该功能尚不兼容 `usingnamespace`,建议尽可能避免使用 `usingnamespace`,根据社区讨论,在未来很可能删除 `usingnamespace`。 + +### x86 后端 + +x86 后端现在通过了 1923 个测试中的 1884(98%)。目前还未作为默认后端使用,但在开发过程中,它通常比 LLVM 后端更好,因为它具有显著更快的编译速度,并且具有更好的调试器支持。 + +该后端接近完成,预计在下一个发布周期使用,作为调试默认的默认后端。建议用户现在就在 `0.14.0` 版本中尝试它。可以使用`-fno-llvm` 或在构建脚本中 `use_llvm = false` 来启用它。 + +### 导入 ZON + +现在可以在编译时导入 ZON: + +```zig +const foo: Foo = @import("foo.zon"); +``` + +目前,这需要已知的结果类型,计划未来取消此限制。 + +有关在运行时导入 ZON 的信息,请参阅升级指南中的 **ZON 解析和序列化**。 + +### 标记器:简化和规范一致性 + +我们用模糊测试器测试了 tokenizer,结果它立刻就崩溃了。检查后,对这个实现不满意。 + +此提交移除了几个机制: + +- 移除了“无效字节”编译错误注释。 +- 通过使恢复总是在换行处发生,而不是其他地方,大大简化了标记器恢复。 +- 移除了 UTF-8 验证。 +- 将一些字符验证逻辑移动到 `std.zig.parseCharLiteral`。 + +移除 UTF-8 验证是对 [#663](https://github.com/ziglang/zig/issues/663) 的回退,然而,现有实现存在问题。当重新添加此功能时,必须在检查其与同一文件上的独立 Unicode 验证实现匹配的属性时进行模糊测试。同时,模糊测试还应检查该提案的其他属性,例如源代码中不存在 ASCII 控制字符,`\r` 总是跟随 `\n`,等等。 + +此提交中包含的其他更改: + +- 弃用 `std.unicode.utf8Decode` 及其 WTF-8 对应函数。此函数的 API 很尴尬,太容易被误用。 +- 使 `utf8Decode2` 及其相关函数使用数组作为参数,消除运行时断言,转而使用类型系统。 + +在此提交之后,由模糊测试发现的崩溃(即 `"\x07\xd5\x80\xc3=o\xda|a\xfc{\x9a\xec\x91\xdf\x0f\\\x1a^\xbe;\x8c\xbf\xee\xea"`)不再导致崩溃。没有必要添加此单元测试,因为简化的逻辑消除了这种性质的崩溃。 + +**Benchmark 1 (100 runs)**: `before/zig ast-check /home/andy/dev/zig/src/Sema.zig` + +| 测量项 | 平均值 ± 标准差 | 最小值 … 最大值 | 异常值比例 | 变化率 | +| ---------------- | --------------- | --------------- | ---------- | ------ | +| wall_time | 50.0ms ± 2.04ms | 48.7ms … 57.4ms | 14 (14%) | 0% | +| peak_rss | 60.0MB ± 147KB | 59.4MB … 60.2MB | 3 (3%) | 0% | +| cpu_cycles | 232M ± 745K | 230M … 234M | 3 (3%) | 0% | +| instructions | 522M ± 24.3 | 522M … 522M | 1 (1%) | 0% | +| cache_references | 6.55M ± 120K | 6.39M … 7.45M | 2 (2%) | 0% | +| cache_misses | 205K ± 3.47K | 198K … 215K | 1 (1%) | 0% | +| branch_misses | 2.86M ± 10.3K | 2.80M … 2.87M | 9 (9%) | 0% | + +**Benchmark 2 (104 runs)**: `after/zig ast-check /home/andy/dev/zig/src/Sema.zig` + +| 测量项 | 平均值 ± 标准差 | 最小值 … 最大值 | 异常值比例 | 变化率 | +| ---------------- | --------------- | --------------- | ---------- | --------------- | +| wall_time | 48.3ms ± 250us | 48.1ms … 50.0ms | 10 (10%) | ⚡- 3.3% ± 0.8% | +| peak_rss | 62.4MB ± 142KB | 62.1MB … 62.6MB | 0 (0%) | 💩+ 4.1% ± 0.1% | +| cpu_cycles | 227M ± 637K | 226M … 230M | 7 (7%) | ⚡- 1.9% ± 0.1% | +| instructions | 501M ± 44.8 | 501M … 501M | 7 (7%) | ⚡- 4.0% ± 0.0% | +| cache_references | 6.65M ± 141K | 6.45M … 7.67M | 4 (4%) | + 1.5% ± 0.5% | +| cache_misses | 208K ± 3.79K | 201K … 226K | 3 (3%) | + 1.3% ± 0.5% | +| branch_misses | 2.84M ± 8.62K | 2.81M … 2.86M | 1 (1%) | - 0.4% ± 0.1% | + +### 在所有函数中包含错误跟踪 + +具体见 PR [#22572](https://github.com/ziglang/zig/issues/22572)。 + +## 链接器 + +### 将输入文件解析移至前端 + +将 GNU `ld` 脚本处理移至前端,以加入相关的库查找逻辑,使得库符合与所有其他库相同的搜索标准。 + +此更改是必要的,以便编译器在编译开始时了解所有链接器输入,从而使链接和编译可以同时开始。在 `flush()` 期间发现链接器输入为时已晚。此分支完全移除了传递给 ELF 链接代码的 `lib_dirs`。 + +不幸的是,这意味着在针对 ELF 时,需要对所有 `.so` 文件进行文件系统访问,以确定它们是否是链接器脚本,因此我引入了一个选择性加入的 CLI 标志来启用 `.so` 脚本。当遇到 GNU ld 脚本时,错误消息会通知用户有关 CLI 标志的信息,该标志将立即解决他们的问题,即传递 `-fallow-so-scripts` 给 `zig build`。这意味着那些没有奇怪库的用户,或者至少避免链接这些库的用户,不必支付这些文件系统访问的成本。 + +所有目标文件、归档文件和共享目标文件的解析现在都在并行编译管道中进行,而不是在最后形成瓶颈。 + +这朝着增量编译迈出了一步。 + +### Wasm 链接器重构 + +目标: + +- 使用 wasm 链接器和后端时编译更快 +- 通过直接将内存中的链接器状态复制到磁盘来启用保存编译器状态 +- 更有效的编译器内存利用 +- 为 wasm 链接器代码引入整数类型安全 +- 生成更好的 WebAssembly 代码 +- 完全参与增量编译 +- 在编译器管道中尽可能多地完成工作,而不是在最后形成瓶颈,同时继续进行垃圾收集 +- 避免不必要的堆分配 +- 避免不必要的间接函数调用 + +所有这些目标都已实现。演示和性能数据点在报告中可用。 + +虽然它通过了之前的相同测试套件,但链接器尚未完成,尚未默认启用,因此不会消除对 LLD 的依赖。 + +### 模糊测试器 + +Zig `0.14.0` 集成了一个模糊测试器。它处于 `alpha` 状态,这意味着使用它需要参与开发。 + +为构建运行器添加了 `--fuzz` CLI 选项。当使用此选项时,它会重新构建包含至少一个模糊测试的任何单元测试二进制文件,并使用 `-ffuzz`,然后告诉它开始模糊测试,这会进行进程内模糊测试。 + +这仅包含模糊测试器逻辑的基本实现,实际上只是一些早期的实验,但已经使这个测试用例在我们的机器上在 65 毫秒内失败: + +```zig +test "fuzz example" { + const input_bytes = std.testing.fuzzInput(.{}); + try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input_bytes)); +} +``` + +`--fuzz` 标志使构建系统生成一个 HTTP 服务器,该服务器提供一个模糊测试器 Web UI,显示模糊测试器探索不同输入时实时更新的代码覆盖率。 + +```sh +$ zig build test --fuzz +info: web interface listening at http://127.0.0.1:38239/ +info: hint: pass --port 38239 to use this same port next time +[0/1] Fuzzing +└─ foo.bar.example +``` + +[asciinema demo](https://asciinema.org/a/asgN7rIr6LFeMhQMOXI825wGe) + +[vedio demo and screenshots](https://github.com/ziglang/zig/pull/20958) + +## Bug 修复 + +在该版本中关闭的 [Issue 列表](https://github.com/ziglang/zig/issues?q=is%3Aclosed+is%3Aissue+label%3Abug+milestone%3A0.14.0) + +## 工具链 + +### UBSan 运行时 + +Zig 现在提供了一个用于 [UBSan](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html) 的运行时库,在 Debug 模式下编译时默认启用。 + +简言之,默认情况下,当在 C 代码中触发未定义行为时,它现在看起来像这样: + +```sh +$ zig run test.c -lc +thread 208135 panic: signed integer overflow: 2147483647 + 2147483647 cannot be represented in type 'int' +/home/david/Code/zig/build/test.c:4:14: 0x1013e41 in foo (test.c) + return x + y; + ^ +/home/david/Code/zig/build/test.c:8:18: 0x1013e63 in main (test.c) + int result = foo(0x7fffffff, 0x7fffffff); + ^ +../sysdeps/nptl/libc_start_call_main.h:58:16: 0x7fca4c42e1c9 in __libc_start_call_main (../sysdeps/x86/libc-start.c) +../csu/libc-start.c:360:3: 0x7fca4c42e28a in __libc_start_main_impl (../sysdeps/x86/libc-start.c) +???:?:?: 0x1013de4 in ??? (???) +???:?:?: 0x0 in ??? (???) +fish: Job 1, 'zig run test.c -lc' terminated by signal SIGABRT (Abort) +``` + +如果默认设置未能正确检测是否应省略或包含运行时库,可以分别使用 `-fno-ubsan-rt` 和 `-fubsan-rt` 覆盖默认设置。相关 [Issue](https://github.com/ziglang/zig/issues/22572)。 + +[开发日志](https://ziglang.org/devlog/2025/#2025-02-24) + +### compiler_rt + +#### 优化的 `memcpy` + +相关 PR [#18912](https://github.com/ziglang/zig/pull/18912) + +### LLVM 19 + +此版本的 Zig 升级到 [LLVM 19.1.7](https://releases.llvm.org/19.1.0/docs/ReleaseNotes.html)。这涵盖了 Clang (`zig cc`/`zig c++`)、libc++、libc++abi、libunwind 和 libtsan。 + +### musl 1.2.5 + +Zig 附带了 musl 的源代码。 + +当选择 musl C ABI 时,Zig 从源代码为所选目标构建静态 musl。 + +Zig 还支持针对动态链接的 [musl](https://releases.llvm.org/19.1.0/docs/ReleaseNotes.html),这对于使用它作为系统 libc 的 Linux 发行版(如 [Alpine Linux](https://www.alpinelinux.org/))非常有用。 + +此版本保持 v1.2.5,但: + +- 应用了 Rich Felker 的 [CVE-2025-26519](https://www.openwall.com/lists/oss-security/2025/02/13/2) 缓解补丁。 +- 应用了许多正在上游处理的特定目标补丁。 +- 更新了动态链接时提供的 libc 存根符号列表。 +- 修复了一些特殊 libc 函数(如软浮点目标上的 `fma`)中的堆栈溢出问题。 +- 此外,Zig 不再附带 musl 的 [`memcpy` 文件](https://github.com/ziglang/zig/pull/22513)。相反,Zig 提供了优化的 `memcpy`。 + +### glibc 2.41 + +在交叉编译时,现在可以使用 glibc 版本 2.40 和 2.41。 + +一些显著的修复: + +- 修复了 Zig 构建的可执行文件未定义 `_IO_stdin_used`,导致它们在多个目标上无法使用的问题。 +- 修复了某些目标(例如 powerpc 和 s390x 上的 `lgammal`)导致重复存根符号的边缘情况。 +- 更改了存根符号,使它们不再具有相同的地址。这导致 LLVM 在某些目标上合并它们,导致代码严重损坏。 +- 应用了一个针对 LLVM 错误的解决方法,该错误导致在针对 mips r6 时 `j $ra` 被错误地汇编。 +- 应用了一个针对 LLVM 的 s390x 汇编器缺少语法支持的解决方法。 + +### Linux 6.13.4 头文件 + +此版本包括 Linux 内核版本 6.13.4 的头文件。 + +### Darwin libSystem 15.1 + +此版本包括 Xcode SDK 版本 15.1 的 Darwin libSystem 符号。 + +### MinGW-w64 + +此版本将捆绑的 MinGW-w64 版本提升到提交 `3839e21b08807479a31d5a9764666f82ae2f0356`。 + +其他更改: + +- Zig 现在捆绑了 `winpthreads` 库。 +- Zig 现在支持为 `thumb-windows-gnu` 进行交叉编译。 + +### wasi-libc + +此版本将捆绑的 wasi-libc 版本提升到提交 `d03829489904d38c624f6de9983190f1e5e7c9c5`。 + +## 路线图 + +`0.15.0` 版本周期的主要目标将是编译速度。 + +一些即将到来的里程碑: + +- 使 x86 后端成为调试模式的默认后端。 +- 增强链接器实现,消除对 LLD 的依赖并支持增量编译。 +- 增强集成的模糊测试器,使其与 AFL 和其他最先进的模糊测试器竞争。 + +这里的想法是,优先提高编译速度将增加编译器本身的开发速度,从而在接下来的发布周期中修复更多的错误并完成更多的功能。 + +为了提高编译速度,可能会对语法进行变动。 diff --git a/course/update/upgrade-0.14.0.md b/course/update/upgrade-0.14.0.md index 119f6c77..7cc0fedc 100644 --- a/course/update/upgrade-0.14.0.md +++ b/course/update/upgrade-0.14.0.md @@ -55,7 +55,7 @@ test "emulate labeled switch" { 与普通的 `switch` 语句不同,带有一个或多个 `continue` 目标的标记 `switch` 语句不会在编译时隐式求值(这类似于循环的行为)。然而,与循环一样,可以通过在 `comptime` 上下文中求值这样的表达式来强制进行编译时求值。 -#### 代码生成属性 +#### 生成的代码特性 这种语言结构旨在生成有助于 CPU 预测 `switch` 各个 `case` 之间分支的代码,从而提高热循环中的性能,特别是那些调度指令、评估有限状态自动机(FSA)或执行类似基于 `case` 的评估的循环。为了实现这一点,生成的代码可能与直观预期的不同。 From 33a8ebcc29708ee7d329a6ff73c2bb542ec8262b Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 16 Mar 2025 16:02:18 +0800 Subject: [PATCH 12/12] little fix --- course/update/upgrade-0.14.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/course/update/upgrade-0.14.0.md b/course/update/upgrade-0.14.0.md index 7cc0fedc..c2504301 100644 --- a/course/update/upgrade-0.14.0.md +++ b/course/update/upgrade-0.14.0.md @@ -432,7 +432,7 @@ fn dec(counter: *RefCounter) void { 如果没有 `@fence` ,这里有两种方法: -- 无条件地通过 fence 的排序来加强所需的原子操作。 +- 无条件地通过栅栏的排序来加强所需的原子操作。 ```zig if (counter.rc.fetchSub(1, .acq_rel) == 1) {