此頁面包含主分支 Zig 近期變更的精選列表。

此頁面包含 2026 年的條目。其他年份可在開發日誌存檔頁面中找到。

既然使用者建置腳本 (build.zig) 和建置系統本身現在有獨立的程序,那麼將套件管理邏輯放在該處是合乎邏輯的。

我已將以下子命令移至 maker 程序:

這意味著編譯器可執行檔中原本包含的大部分內容,現在改以原始碼形式提供,包括:

因此,這些功能現在可以在不重新建置編譯器的情況下進行修補,讓使用者和貢獻者更容易進行實驗。

此外,這也意味著 Zig 中的套件管理在進行網路操作時,現在已啟用安全檢查,因為 maker 可執行檔是以 ReleaseSafe 模式編譯的。而且,用於網路和檔案雜湊的所有加密,現在都可以利用主機上可用的特殊 CPU 指令,即使是那些在軟體分發時通常不依賴的指令。我們可以同時擁有 AOT 的優勢並享受 JIT 的便利!

我最初這樣做的動機是為了公開建置伺服器協定,以解決在 maker/configurer 程序分離對 `--build-runner` 覆寫旗標進行破壞性變更後,ZLS 遇到的阻礙。

最初,程序樹看起來是這樣的:

程序分離的變更集使其看起來像這樣:

此時,考慮一個長時間執行的 `zig build --watch` 程序,它會監控檔案並在原始碼變更時重新建置。如果偵測到 `build.zig` 的任何變更,或在執行該邏輯期間觀察到的任何檔案,都意味著 configurer 需要重新執行,這表示 maker 程序必須退出,以便 `zig build` 有機會重複套件管理邏輯。

現在,根據此開發日誌條目所述的變更後,看起來是這樣的:

因此,當組態需要重新執行時,maker 程序可以繼續存在,因為它是父程序而不是同級程序。對於即將推出的建置伺服器而言,這意味著可以避免伺服器退出且客戶端必須重新連接的尷尬情況,而不是僅僅通知客戶端組態變更。

這幾乎完全是一個非破壞性變更,但有一些可觀察到的差異:

此變更集後續的問題是我們標記 Zig 0.17.0 的主要阻礙:

七月我有兩個會議要參加,需要準備演講,所以現實一點來說,我認為要到八月初才有時間完成這些工作。當然,非常歡迎貢獻。

非常感謝 ZLS 團隊的 Techatrix 主動聯繫並與我合作開發建置伺服器協定!順帶一提,他們正在尋求贊助。

有很多內容要涵蓋。在最近的編譯器變更後,SPIR-V 後端在許多地方都出現了位元腐蝕,因此我花了過去幾週的時間將其恢復到更好的狀態。

SPIR-V 有一些無法在 Zig 的類型系統中表達的類型。新的 `@SpirvType` 內建函式已被引入,以解決編寫著色器時最長期的阻礙。請參閱 #20550、#23326 和 #35461 來追蹤背景。

執行模式資訊(工作群組大小、片段原點等)現在由呼叫約定攜帶,而不是透過內嵌組合語言 `OpExecutionMode` 發出。舊的 `std.gpu.executionMode()` 輔助函式已移除,SPIR-V 組合器現在會拒絕手動的 `OpExecutionMode` 指令。還為網格著色管線添加了兩個新的呼叫約定:`spirv_task` 和 `spirv_mesh`。

功能和擴充功能過去由程式碼產生或透過內嵌組合語言隨機發出。現在它們完全由 CPU 功能集驅動,就像其他目標一樣,依賴鏈從 SPIRV-Headers 中提取(目前不包括外部供應商),組合器現在會拒絕任何直接發出 `OpCapability` 或 `OpExtension` 的嘗試。

從第一天起,SPIR-V 後端就在連結器執行緒內單線程運行程式碼產生。每個程式碼產生作業現在都會產生一個 Mir 值,就像所有其他自託管後端一樣,並在編譯器的執行緒集區上排程。

相同的變更帶回了兩個在早期重構中被移除的 ISel 傳遞:`dedup_types`(合併等效類型指令)和 `prune_unused`(從最終模組中剝離死碼)。這些最初是在程式碼產生為單線程時被刪除的。

`.spv` 檔案現在被識別為物件檔案。您可以編譯多個 `.zig` 檔案(或外部 `.spv` 物件),讓 SPIR-V 連結器將它們縫合成一個單一模組。

在此過程中還修復了數十個錯誤,在 spirv64-vulkan 目標上的總體行為測試通過率提高了近 10%(現為 49%),`std.gpu` 已重新命名為 `std.spirv`,SPIR-V 後端比一個月前更有用,但仍有很長的路要走。SPIR-V 上仍有許多行為測試被跳過。也就是說,如果您一直在猶豫是否要嘗試 Zig 來開發著色器或計算核心,現在是個好時機。非常歡迎在 Codeberg 上回報錯誤。祝您編碼愉快!

(開發日誌內容相當長,抱歉—這次我有點太投入了!)

幾週前,我開始著手處理一個分支,該分支實現了對 LLVM 後端的長期規劃的改進。這演變成了一個更大的變更,實現了幾個您可能感興趣的語言提案。

Zig 一直將任意位元寬度的整數類型(例如 `u4`、`i13`、`u40`)直接降低到 LLVM IR 的位元整數類型(`i4`、`i13`、`i40`)。然而,我們早就知道這種降低不是最佳的,因為 LLVM 對於在記憶體中表示這些類型的文件化語義對優化器來說過於嚴格。更重要的是,由於 Clang 從未這樣發出 LLVM IR,LLVM 中的這些程式碼路徑從未得到適當測試,因此實際上支援不佳—在過去幾年中,我們觀察到許多簡單的優化被錯過,甚至出現了直接的錯誤編譯。

因此,PR 的原始目標只是在 SSA 形式中操作值時使用這些位元整數類型,並在將它們儲存在記憶體中時將它們擴展為 ABI 大小的類型(`i8`、`i16`、`i32` 等)。這應該得到良好的支援,尤其因為它與 Clang 如何降低 C 的 `_BitInt(N)` 相符!

該變更實際上相當直接,但我遇到了一個問題,導致我陷入了一個無底洞。

`@bitCast` 是一個有趣的內建函式。過去,它被定義為等同於以下操作序列:

換句話說,它基本上是重新解釋記憶體位元組的語法糖。然而,隨著時間的推移,我們偏離了這個定義—例如,允許使用 `@bitCast` 將 `[3]u8` 重新解釋為 `u24`,即使在大多數目標上 `@sizeOf(u24)` 大於 `@sizeOf([3]u8)`,因此上述定義會引發非法行為。

到目前為止,LLVM 後端一直實現 `@bitCast` 內建函式的這些未定義語義。然而,由於該定義涉及重新解釋記憶體,改變我們在記憶體中儲存整數類型的方式最終影響了 `@bitCast` 的實作,並引入了非法行為,導致編譯器測試套件崩潰。

最簡單的解決方案可能是實作 LLVM 後端的邏輯來近似匹配舊行為。但我選擇了一個更好的解決方案—實作 `@bitCast` 的新定義。

2024 年,Jacob Young 撰寫了語言提案 #19755,旨在透過精確定義一組新的語義來解決 `@bitCast` 的問題。該提案不久後被接受,事實上,它詳細說明的語義已經被自託管的 x86_64 後端實作!因此,為了解決 LLVM 後端的問題,我不一定需要匹配舊的 `@bitCast` 語義—相反,現在是時候在所有地方實作新的語義了。

順帶一提,這樣做的另一個優點是我們可以利用編譯器的 Legalize 傳遞,它會將難以降低的操作改寫為更簡單的操作,以便編譯器後端只需要支援這些簡單的操作。Legalize 已經具有功能,被自託管的 x86_64 後端使用,它將複雜的 `@bitCast` 操作轉換為更簡單的操作,並且可以輕鬆地適應以協助其他編譯器後端(主要是 LLVM 和 C 後端)—但前提是它們實作了新的語義。

無論如何,重點是,我開始了一次支線任務(結果比原始任務更難),在整個編譯器中實作這些新的語義。這不僅包括 LLVM 和 C 後端,還包括 comptime 執行—畢竟,Zig 允許您在 comptime 執行幾乎任何操作,包括 `@bitCast`!由於新語義與舊語義有顯著差異(稍後會詳細介紹),我還必須審查標準函式庫、編譯器和支援函式庫(例如 compiler_rt)中對 `@bitCast` 的許多用法。但在經過一些大致順利的 CI 失敗修復後,我終於讓我的 PR 變綠,並在昨天合併到 master 分支(同時關閉了幾個問題!)。

現在我們已經完成了所有背景介紹,終於到了實際解釋新的 `@bitCast` 行為的時候了。它不再基於重新解釋記憶體位元組,而是定義為基於邏輯上代表類型的位元。

每個支援 `@bitCast` 的類型都有一個「邏輯位元佈局」—將該類型表示為一序列的位元。例如,`u5` 由 5 個邏輯位元組成,我們從最低有效位元到最高有效位元排序。`[2]u5` 由 10 個邏輯位元組成—第一個元素的 5 個,接著是第二個元素的 5 個。新的 `@bitCast` 定義是它重新解釋一個類型的邏輯位元為另一個類型的邏輯位元。

最簡單的例子是取一個無符號整數,例如 `u8`,並將其轉換為相同大小的有符號整數,在本例中為 `i8`。此操作的結果正如您所預期的—位元保持不變,我們只是重新解釋最高有效位元作為符號位元。在整數類型和打包結構/打包聯合類型之間的 `@bitCast` 的語義也保持不變。

新語義與舊語義不同的地方在於涉及聚合類型(陣列和向量)時。

例如,考慮將 `[2]u8` 位元轉換為 `u16`。在舊語義下,此操作的結果取決於目標位元組順序:在大端序目標上,第一個陣列元素變成了 8 個最高有效位元,而在小端序目標上,第一個陣列元素變成了 8 個最低有效位元。在新語義下,因為我們只關心邏輯位元佈局(與位元組順序無關),操作在每個目標上的行為都相同:第一個陣列元素變成了 8 個最低有效位元。一般而言,新語義傾向於匹配舊語義在小端序目標上的行為。

此定義還允許一些較奇怪的操作,例如將 `[2]u3` 轉換為 `@Vector(3, u2)`:

這種操作大多數時候沒有太大用處,但如果您需要它,它就在那裡!例如,您可能想將一個整數解構為一個由單獨位元組成的向量進行操作—現在可以透過 `@bitCast` 到 `@Vector(n, u1)` 來完成。

在進行所有這些工作時,我也實作了幾個較小的已接受提案—我不會在此詳細介紹,但如果您有興趣,可以查看這些問題:

當然,所有這些變更的語義都將在 0.17.0 的發行說明中進行解釋(希望比我在此處的說明更簡潔!),並概述建議的遷移步驟。

最後一點,我想提一下,這個分支的最初動機—改變 LLVM 後端如何降低非 ABI 整數類型—在恢復錯過的優化方面已被證明是成功的。事實上,Zig 編譯器本身—儘管在內部沒有大量使用任意位元寬度的整數!—由於更好的優化,性能提高了約 5%。這意味著您在 0.17.0 中可能會看到一些輕微的執行時性能提升!

感謝您的閱讀,希望這對您有所幫助 :)

在過去幾週裡,我一直在處理我們新的 ELF 連結器,該連結器在 Zig 0.16.0 中首次亮相。在 0.16.0 發布時,這個連結器實作還處於相當早期的階段,並且主要只支援連結純 Zig 程式碼,沒有任何外部函式庫(甚至 libc)—因此它預設是禁用的(可以透過 `-fnew-linker` 啟用)。然而,自最初發布以來,已經取得了相當大的進展!

這是一個不錯的里程碑—截至我最新的 PR,新的 ELF 連結器能夠建置自託管的 Zig 編譯器,並啟用 LLVM 和 LLD 函式庫,這項任務需要許多底層功能。

當然,ELF 連結器不一定是世界上最令人興奮的東西,這就是為什麼這個新連結器的主要特色是支援快速增量編譯。在最近的增強功能之後,現在(在 x86_64 Linux 上)可以在連結外部函式庫、C 原始碼等時執行增量重建,而沒有額外的效能開銷!這是我嘗試在 Andrew 的 Tetris 克隆上進行測試的片段:

Andrew 的 Tetris 克隆建置的幾個小變更,每個大約 30 毫秒。

哦,快速增量重建在 Zig 編譯器本身上也運行良好:

目前這個連結器實作最大的缺失功能是它仍然不支援為 Zig 程式碼產生 DWARF 除錯資訊—這絕對是我的下一個優先事項。但即使沒有這個支援,即時重建功能有多麼有用也是驚人的,例如在任何需要大量列印除錯的情況下。

如果您正在使用 Zig 的 master 分支並且在 x86_64 Linux 上,請考慮嘗試使用新的 ELF 連結器進行增量編譯,如果它先前與您的專案不適用!我預計許多程式碼庫已經可以很好地與之配合,能夠在毫秒內重建您的專案。

當然,如果您遇到任何錯誤,請開啟一個 issue。

如果您目前仍在使用 Zig 的標記版本,請不用擔心—正如 Andrew 在他上次的開發日誌中所提到的,Zig 0.17.0 很快就會推出,所以您很快就可以嘗試這個功能了!

一個大型分支剛剛合併:將 maker 程序與 configurer 程序分離

此開發日誌條目本質上是即將發布的發行說明預覽,但它為那些想協助測試新功能並提供指導 Zig 專案未來發展的意見回饋的人提供了預先通知。

以前,`build.zig` 檔案和建置系統實作都編譯成一個臃腫的程序,以 Debug 模式運行。在 `build.zig` 邏輯完成在記憶體中建構建置圖後,「建置執行器」程式碼會執行它。

現在,`build.zig` 檔案被編譯成一個小型程序(「configurer」),以 debug 模式運行。在該邏輯完成在記憶體中建構建置圖後,它會被序列化為一個二進位組態檔案。父級 `zig build` 程序知道這個檔案,並在下次使用時快取它。在等待所有這些的同時,它會異步地以 release 模式編譯建置圖執行程序(「maker」)。一旦組態檔案可用且 maker 程序完成編譯,maker 程序就會被執行,並將組態檔案傳遞給它。maker 程序只需要為每個 Zig 版本編譯一次,這要歸功於全域快取。然後,maker 程序執行建置圖,該建置圖包含在序列化的組態檔案中。

此變更的主要動機是透過三種方式讓 `zig build` 變得更快:

每次變更時,只有使用者的 `build.zig` 邏輯會被編譯,而不是整個建置系統。隨著我們引入 `--watch`、`--fuzz` 和 `--webui`,這開始變得更有價值。建置系統可以增加更多功能,而不會增加 `zig build` 的時間。

現在,當建置系統知道沒有任何內容會改變時,它可以完全跳過重新執行 `build.zig` 邏輯,例如,如果您在 `zig build` 命令列中添加 `-freference-trace`,它現在可以避免冗餘地重新執行您的 `build.zig` 邏輯,使用與上次相同的組態。

現在,實際執行建置圖的程序是以最佳化啟用的狀態編譯的。

為了演示第 2 和第 3 點,這是執行 `zig build --help` 在變更前後的差異:

這是戲劇性的,因為以前,每次執行 `zig build` 命令時都會執行 `build.zig` 邏輯,但現在,建置系統使用快取的、序列化的組態。

除了效能之外,我預計第三方工具(如 ZLS)將受益於使用序列化的組態檔案,而不是維護建置執行器的分支。

此變更集極大地重構了 Zig 建置系統的內部機制,但從 API 角度來看,它大多是非破壞性的,除了上述 PR 中提到的例外情況。

對大多數人來說,我猜測這是他們會遇到的主要破壞性變更:

這從建置腳本中移除了一項功能,因為它們無法再觀察到這些引數。作為交換,這意味著當更改這些引數時,建置腳本不再必須從原始碼重新建置。

如果您是想影響 Zig 方向的人,現在是將您的專案升級到開發版本並嘗試這些變更的好時機。我們將在幾週內發布 0.17.0。但是,如果您沒有時間,並且發現 0.17.0 破壞了您的建置,請不用擔心,在 0.17.1 版本中仍有許多機會可以進行修復。

上個月合併我的類型解析變更後,我花了一些時間處理個人專案,但最近我找到了時間對 LLVM 程式碼產生後端進行了一些改進。這涉及了幾個具有不同目標的增強功能,但一個不錯的面向使用者的變更是我成功地讓增量編譯與 LLVM 後端一起工作。

可惜的是,這無法加快 LLVM 發出物件檔案的過程:該時間完全取決於 LLVM。然而,增量編譯有助於最大限度地縮短實際 Zig 編譯器程式碼的執行時間,這意味著如果您的程式碼有編譯錯誤(因此會跳過「LLVM 發出物件檔案」),您通常會非常快速地收到這些錯誤。(當然,在成功建置時它仍然會為您提供輕微的加速。)

此支援目前在 master 分支建置中可用,並將包含在 0.16.0 版本中(我們很快就會標記)。

對於任何還沒有嘗試過的人,特別是如果您正在使用 Zig 的 master 分支,請透過傳遞 `-fincremental --watch` 給 `zig build` 來嘗試增量編譯!Zig 核心團隊在我們的開發流程中已經受益於增量編譯一年了,我們也聽到了使用者們的好評。該功能目前相對穩定,人們經常驚訝於他們可以節省多少時間,僅僅是獲得最新的編譯錯誤只需幾毫秒而不是幾秒鐘。

我個人還沒有真正使用過 LLVM 後端的增量編譯,但 CI 中的所有增量測試覆蓋範圍現在都已為 LLVM 後端啟用,並且我收到了使用者的正面回饋,所以絕對值得一試。一如既往,如果您在增量編譯中遇到錯誤,請盡可能回報!

謝謝您,希望您覺得這很有用 :)

今天,我合併了一個 30,000 行的 PR,這是兩個(可以說是三個)月的成果。這個分支的目標是將 Zig 編譯器的內部類型解析邏輯重構為一個更邏輯化、更直接的設計。這對我個人來說是一個非常令人興奮的變更,因為它讓我能夠清理編譯器內部的大量程式碼,但它也帶來了一些您可能感興趣的、面向使用者的變更!

首先,Zig 編譯器現在對類型欄位的分析更加惰性:如果該類型從未初始化,那麼 Zig 就沒有必要關心該類型「看起來」如何。這在您擁有一個同時作為命名空間的類型時很重要,這是現代 Zig 中的常見模式。例如,在使用 `std.Io.Writer` 時,您不希望編譯器也引入 `std.Io` 中的大量程式碼!這是一個簡單的例子:

先前,此程式碼會發出編譯錯誤。現在,它可以正常編譯,因為 Zig 從未實際查看 `@compileError` 呼叫。

我們所做的另一項改進是「依賴迴圈」體驗。任何先前在 Zig 中遇到依賴迴圈編譯錯誤的人都知道,它們的錯誤訊息完全沒有幫助—但現在已經改變了!如果您遇到一個(現在也比以前不太可能發生),您將收到詳細的錯誤訊息,準確告訴您依賴迴圈來自何處。看看這個:

當然,依賴迴圈可能比這複雜得多,但在我測試過的每個案例中,錯誤訊息都提供了足夠的資訊來輕鬆了解情況。

此外,此 PR 對 Zig 編譯器的「增量編譯」功能進行了重大改進。簡而言之,它修復了大量已知錯誤,但特別是,「過度分析」問題(即增量更新做了比必要工作更多的工作,有時甚至多出很多)應該幾乎被消除了—這使得在許多情況下增量編譯顯著更快!如果您還沒有嘗試過,請考慮嘗試增量編譯:它確實是一個愉快的開發體驗。這無疑是我最興奮的改進,也是促成這次變更的很大一部分動機。

此 PR 還帶來了更多變更—數十個錯誤修復,一些小的語言變更(大多相當冷門),以及編譯器效能改進。內容太多無法在此一一列出,但如果您有興趣閱讀更多相關資訊,可以查看 Codeberg 上的 PR—當然,如果您遇到任何錯誤,請開啟一個 issue。祝您編碼愉快!

隨著我們接近 0.16.0 發布週期的尾聲,Jacob 辛勤工作,讓 `std.Io.Evented` 跟上所有最新的 API 變更:

這兩者都基於使用者空間堆疊切換,有時稱為「fibers」、「stackful coroutines」或「green threads」。

現在可以透過建構使用 `std.Io.Evented` 的應用程式來進行實驗。它們應被視為實驗性的,因為在它們能夠可靠且穩健地使用之前,還有重要的後續工作要做:

考慮到這些注意事項,我們似乎確實正在到達應許之地,Zig 程式碼可以輕鬆地交換 I/O 實作:

僅交換 I/O 實作:

這裡的關鍵點是,這兩個程式碼片段之間的 `app` 函式是相同的。

除了「Hello World」之外,Zig 編譯器本身在使用 `std.Io.Evented` 時運行良好,無論是使用 io_uring 還是 GCD,但如上所述,這樣做時存在尚未診斷的效能下降問題。

如果您有一個帶有依賴項的 Zig 專案,兩個重大變更剛剛落地,我認為您會對此感興趣。

抓取的套件現在儲存在專案根目錄的 `zig-pkg` 目錄中(緊鄰您的 `build.zig` 檔案)。

例如,以下是運行 `zig build` 後 awebo 的一些結果:

強烈建議將此目錄添加到專案本地原始碼控制忽略檔案(例如 `.gitignore`)。然而,由於它位於 `.zig-cache` 之外,它提供了分發獨立的原始碼 tarball 的可能性,其中包含所有依賴項,因此可用於離線建置或用於存檔。

同時,依賴項的額外副本被快取在全域範圍內。在根據路徑篩選器過濾掉所有未使用的檔案後,內容會被重新壓縮:

此變更的動機是讓實驗更容易。請隨意編輯這些檔案,看看會發生什麼。將您的套件目錄與 git clone 進行交換。一起 grep 您的依賴項。設定您的 IDE 以根據 `zig-pkg` 目錄進行自動完成。在您的依賴樹上運行 baobab。此外,透過讓全域快取包含壓縮檔案,可以更容易地在電腦之間共用該快取資料。未來,計劃支援依賴樹的點對點 torrenting。透過將套件重新壓縮為標準形式,這將允許對等節點以最少的頻寬共用 Zig 套件。我喜歡這個想法,因為它同時提供了對網路中斷的韌性,以及一個受歡迎程度的競賽。根據種子數量找出哪些開源套件很受歡迎!

第二個變更是為 `zig build` 添加了 `--fork` 旗標。

回想起來,這似乎很明顯,我不知道為什麼一開始沒有想到它。它看起來像這樣:

這是一個專案覆寫選項。給定一個專案原始碼 checkout 的路徑,整個依賴樹中所有匹配該專案的套件都將被覆寫。

由於套件內容雜湊包含名稱和指紋,這會在套件可能被抓取之前解析。

這是一種暫時使用位於完全獨立目錄中的一個或多個 fork 的簡單方法。您可以迭代整個依賴樹,直到一切正常,同時舒適地使用依賴專案的開發環境和原始碼控制。

它是一個 CLI 旗標,使其適當地短暫。

一旦您刪除旗標,您就回到了使用乾淨、抓取的依賴樹。

如果專案不匹配,則會發生錯誤,防止混淆:

如果專案匹配,您會收到一個提醒,您正在使用一個 fork,防止混淆:

此功能旨在增強處理生態系統中斷的工作流程。我已經稍微嘗試過它,發現它工作起來相當愉快。新的工作流程如下:

...而且您可能可以跳過將 `build.zig.zon` 切換到您的 fork 的步驟,除非您預計上游需要很長時間才能合併您的修復。

Windows 作業系統提供了大量的 ABI 介面來在核心中執行操作。然而,並非所有 ABI 都生而平等。正如 Casey Muratori 在他的講座《唯一不可打破的法則》中所指出的,軟體開發團隊的組織結構直接影響他們產生的軟體結構。

Windows 上的 DLL 被組織成一個層級結構,其中一些 API 是較低級 API 的高級包裝器。例如,每當您呼叫 `kernel32.dll` 的函式時,實際工作最終由 `ntdll.dll` 完成。您可以使用 ProcMon.exe 並檢查堆疊追蹤直接觀察到這一點。

我們透過經驗學到的是,`ntdll` API 通常設計良好、合理且功能強大,但 `kernel32` 包裝器會引入不必要的堆疊分配、額外的故障模式、無意的 CPU 使用和膨脹。

這就是為什麼 Zig 標準函式庫的政策是偏好原生 API 而非 Win32。我們還沒有完全達到這個目標—我們仍然有許多對 `kernel32` 的呼叫—但我們最近取得了巨大的進展。我將舉兩個例子。

根據官方文件,Windows 沒有直接取得隨機位元組的方法。

包括 Chromium、boringssl、Firefox 和 Rust 在內的許多專案都呼叫 `advapi32.dll` 中的 `SystemFunction036`,因為它在 Windows 8 及更早版本上都能正常工作。

不幸的是,從 Windows 8 開始,第一次呼叫此函式時,它會動態載入 `bcryptprimitives.dll` 並呼叫 `ProcessPrng`。如果載入 DLL 失敗(例如由於系統過載,我們在 Zig CI 上觀察到幾次),它會返回錯誤 38(來自一個返回 void 且文件說明永不失敗的函式)。

`ProcessPrng` 首先會進行堆疊分配少量固定位元組。如果失敗,它會在 BOOL 中返回 `NO_MEMORY`(文件說明行為是永不失敗,並始終返回 TRUE)。

`bcryptprimitives.dll` 每次載入時似乎還會運行一個測試套件。

`ProcessPrng` 真正做的事情是對 `\Device\CNG` 執行 `NtOpenFile` 並使用 `NtDeviceIoControlFile` 讀取 48 位元組來取得種子,然後初始化一個每 CPU 的基於 AES 的 CSPRNG。

因此,可以避免對 `bcryptprimitives.dll` 和 `advapi32.dll` 的依賴,也可以避免第一次 RNG 讀取時的非確定性故障和延遲。

提醒一下,上述函式是透過呼叫以下函式來實作的。

我們已經可以看到使用較低級 API 的一些好處。例如,真實的 API 直接將錯誤碼作為返回值,而 `kernel32` 包裝器將狀態碼隱藏在某處,返回一個 BOOL,然後要求您呼叫 `GetLastError` 來找出出了什麼問題。想像一下!從函式傳回值 🌈

此外,`OVERLAPPED` 是一個虛設類型。Windows 核心根本不知道或不在乎它!這裡的實際原語是事件、APC 和 `IO_STATUS_BLOCK`。

如果您有一個同步檔案控制代碼,那麼 `Event` 和 `ApcRoutine` 必須為 null。您會在 `IO_STATUS_BLOCK` 中立即獲得答案。如果您在此處傳遞 APC 例程,那麼一些舊的位元腐蝕的 32 位元程式碼將運行,您將獲得垃圾結果。

另一方面,如果您有一個非同步檔案控制代碼,那麼您需要使用 `Event` 或 `ApcRoutine`。`kernel32.dll` 使用事件,這意味著它正在進行額外的、不必要的資源分配和管理只是為了從檔案讀取。相反,Zig 現在傳遞一個 APC 例程,然後呼叫 `NtDelayExecution`。這與取消操作無縫整合,使得在檔案 I/O 執行期間可以取消任務,無論檔案是以同步模式還是非同步模式打開的。

有關此主題的更深入探討,請參閱此 issue:

Windows:偏好原生 API 而非 Win32

在過去一個月左右的時間裡,幾位有進取心的貢獻者對 zig libc 子專案產生了興趣。這個想法是透過提供 libc 函式作為 Zig 標準函式庫包裝器,而不是作為 vendored 的 C 原始碼檔案,來逐步刪除冗餘程式碼。在許多情況下,這些函式是一對一的映射,例如 `memcpy` 或 `atan2`,或者輕鬆地包裝一個通用函式,例如 `strnlen`:

到目前為止,Zig 儲存庫中已刪除了大約 250 個 C 原始碼檔案,仍有 2032 個檔案。

隨著每個函式完成轉換,Zig 獲得了對第三方專案和 C 程式語言的獨立性,編譯速度提高,Zig 的安裝大小得到簡化和縮減,並且靜態連結 libc 的使用者應用程式的二進位檔大小也減少了。

此外,最近的一項增強功能現在使 zig libc 與其他 Zig 程式碼共享 Zig 編譯單元,而不是作為單獨的靜態歸檔,稍後再連結。這是 Zig 擁有整合式編譯器和連結器的一個優勢。當匯出的 libc 函式共享 ZCU 時,由於函式可以一起優化,因此消除了冗餘程式碼。這有點像在 libc 邊界啟用 LTO(連結時優化),只是它是在前端正確完成,而不是太晚在連結器中完成。

此外,當這項工作與最近的 `std.Io` 變更結合時,使用者有可能無縫控制 libc 如何執行 I/O—例如,強制所有讀取和寫入呼叫參與 io_uring 事件迴圈,即使該程式碼不是為這種用例編寫的。或者,可以為第三方 C 程式碼啟用資源洩漏偵測。目前這只是一個未經實驗的虛構想法,但這個想法讓我著迷。

非常感謝 Szabolcs Nagy 的 libc-test。這個專案在確保我們不會退化任何數學函式方面提供了巨大的幫助。

提醒我們的使用者,既然 Zig 正在轉變為靜態 libc 提供者,如果您遇到 Zig 提供的 musl、mingw-w64 或 wasi-libc 功能的問題,請先在 Zig 中提交錯誤報告,這樣我們就不會因為 Zig 中存在錯誤而打擾維護者,並且不再由獨立的 libc 實作專案進行 vendored。

就在我像個懦夫一樣在家寫這篇開發日誌的同一天,不到五英里外,在我城市裡與我們民選官員意願相悖的武裝部隊,向和平抗議者發射了催淚瓦斯,毫無預警。下次我希望有勇氣加入我的鄰居,我希望不會像 Alex Pretti 和 Renée Good 那樣被槍擊。