OCaml 編譯器 - 2021 年 11 月至 2022 年 2 月

我很樂意發布第五期「OCaml 編譯器開發電子報」。您可以使用 compiler-newsletter 標籤找到所有期數。

注意:電子報的內容絕非詳盡無遺,只有少數編譯器維護者和貢獻者有時間撰寫內容,這完全可以接受。

當然,歡迎隨時評論或提問!

如果您一直在開發 OCaml 編譯器並想說些什麼,請隨時在此討論串中發文!如果您希望我在下次準備電子報時(未來某個隨機時間點)與您聯繫,請透過討論區訊息或電子郵件(gabriel.scherer 在 gmail)告知我。

背景

上一期 (2021 年 10 月)對應於合併 Multicore OCaml 實作和「循序凍結」(凍結非多核心相關的變更,以利 Multicore 合併)之前的最後一個開發週期。

自那時以來,Multicore 團隊當然做了大量工作(請參閱 2022 年 1 月的龐大 Multicore 電子報)。上游開發速度不同尋常:非多核心活動比以前少(4.14 版本的最後一刻變更,以及長期專案並行推進),有相當多的工作在清理 Multicore 合併破壞的東西,湧入了新的貢獻者,也有一些非新貢獻者,他們在 Multicore 執行階段方面仍然是完全的初學者。

進展順利,我們預計在某個時候發布 5.0 版 :-)

個人報告

@gasche Gabriel Scherer

形狀

在 #10718 中,@voodoos Ulysse Gérard、@trefis Thomas Refis 和 @lpw25 Leo White 提出了「形狀」,這是一種關於 OCaml 模組的靜態資訊的新形式,將由 OCaml 編譯器計算和儲存,以幫助其他工具,尤其是 Merlin,處理 OCaml 名稱/定義。(這完全是他們的工作,不是我的!)該工作已合併到 4.14 開發版本中。

合併後,@kit-ty-kate Kate Deplaix 的 opam 範圍測試揭示了一些函式程式碼的效能問題,尤其是 Irmin。編譯器中的形狀計算會導致計算時間和/或生成的建置成品大小暴增。

「形狀」的核心機制是 lambda 項的評估器。在 #10825 中,我開發了一個更有效率的評估器(在 @Edkohibs Nathanaëlle Courant 的協助下),使用相對先進的機制(強大的按需呼叫歸約),並且在實踐中取得了良好的結果——不再有已知時間或大小暴增的情況。不同的設計和實作選擇之間有很多來回,Ulysse 的額外測試幫助很大!最後,我們與 Ulysse、Thomas、@Octachron Florian Angeletti 和 Nathanaëlle 舉行了一次面對面的審查會議,這非常有趣,尤其是在這種面對面活動較少的時期。

GADT 和模式泛化

在 #1097 中,@dongyan 報告了 OCaml 型別檢查器中的一個健全性錯誤,該錯誤是由於在模式匹配的型別推斷中多型性(泛化)和 GADT 存在型別的相互作用所致。我們分析了這個問題,我提出了一個型別規則的限制,以拒絕不健全的範例;@lpw25 Leo White 進一步完善了這個提案。我嘗試自己實作這個修復/限制,但是當不熟悉 OCaml 程式碼庫時,這並不容易。Jacques Garrigue 在 #10907 中提出了完整的實作,現在已合併。(這僅實作了我最初的標準,而不是 Leo 的改進,這在目前的模式型別推斷實作中更難實作。)

初學者級別的多核心駭客

現在,我們鼓勵 OCaml 維護者溫和地參與與多核心相關的合併後任務,而不是偷懶地從事酷炫的優化或型別系統錯誤。(上面的兩個任務的藉口是它們有助於準備 4.14 版本。)但是大多數人在幾個月前對多核心執行階段一無所知,所以這裡每個人都是初學者!

我研究了小的重構或次要錯誤修復,因為我在閱讀程式碼時發現了它們,其中有三個較大的部分

  1. #10887:我繼續與 @xavierleroy 合作開發 Domain.DLS 介面,以允許 OCaml 函式庫儲存每個網域的全域狀態;現在可以建立在 Domain.spawn 上「繼承」的每個網域的狀態(子狀態是從父狀態計算出來的)。這是用可分割的隨機數產生器取代 Random 實作的必要基礎構件,Xavier 最終按照計劃在 #10742 中使用 LXM 完成了這個工作。

  2. #10971:@sabine Sabine Schmaltz 的這個問題討論了當使用者要求較大的次要堆積或更多網域時,如何變更用於次要堆積的保留記憶體位址空間的大小。(每個網域都有自己的次要堆積,但它們是連續的,以便快速 Is_young 檢查。)Sabine 和我正在開發實作。我發送了各種準備 PR,例如 #10974(變更計算唯一網域識別碼的機制,該機制依賴於固定的 Max_domains 限制)。

  3. gc 統計資料(#11008、#11047):計算 GC 統計資料的程式碼需要一些改進,它在多核心執行階段中發生了重大變化,但也試圖保留先前的 GC 公開的介面,而且有些地方略有錯誤。我開始從 Max_domains 的角度閱讀程式碼(它將每個網域的統計資料儲存在固定大小的陣列中),但最終在 @Engil Enguerrand Decorne 的幫助下開始處理它。

@xavierleroy Xavier Leroy

2022 年 1 月 10 日,我很榮幸在提取請求 #10831 上按下「合併」按鈕,從而將 Multicore OCaml 引入 OCaml「主幹」,並誕生了 OCaml 5。

在這個光榮的時刻之前和之後,Multicore OCaml 開發團隊、其他審查人員和我花費了大量時間研究和審查 Multicore OCaml 來源、審查大型提取請求,並修復合併後仍然存在的問題。我也花了很多時間重新設計我們的 Jenkins CI 系統來處理 OCaml 5,並相應地調整測試套件。

過渡到 OCaml 5 也是移除長期過時的功能並簡化程式碼庫的好機會。例如,在 #10898 中,我能夠移除大量信號處理的雜亂程式碼,這些程式碼現在不再相關,因為信號處理常式不再在收到信號時立即運行,而堆疊溢位由 ocamlopt 生成的程式碼明確管理。另一個範例是 #10935,它棄用了 Thread.exit 函數,並提供了一種更簡單的、基於例外情況的機制來提前終止執行緒。

最後但並非最不重要的是,由於 @gasche 在網域本機狀態方面的工作,我終於能夠在可愛的 LXM 偽隨機數產生器的基礎上合併我對 Random 模組的重新實作(#10742)。

@Octachron Florian Angeletti

針對形狀的編譯時間和編譯成品大小進行基準測試

如 @gasche 所述,OCaml 4.14 引入了一種稱為 shape 的新型中繼資料。形狀中繼資料的計算在編譯時間方面會產生一些成本。在初始審查期間,我完全低估了這個成本,這導致了 irmin 回報的編譯時間暴增。當需要修復這個錯誤時,我希望更全面地了解形狀計算對 opam 儲存庫的重要部分的影響。因此,我測量了超過一千個 opam 套件(opam 儲存庫中具有最多反向相依性的那一半)在有無形狀計算的情況下的編譯時間和編譯成品大小。幸運的是,此效能測量活動的結論是,對於 90% 的原始程式碼檔案,編譯時間的增加不到 10%

| 百分位數 | 相對編譯時間增加 | |---|------------------------| | 1% | -9% | | 10% | 0% | | 25% | 0% | | 50% | 0% | | 75% | +4% | | 90% | +10% | | 99% | +20% | | 99.9% | +32% | | 99.99% | +46% |

新時代的文件標籤

使用 Multicore OCaml 時,OCaml 函式庫需要記錄它們在多核心環境中使用的安全性,尤其是在它們使用某些全域狀態時。為了讓記錄這一點盡可能容易,我開始在 #10983 中實作新的 ocamldoc 標籤,以實現多核心安全。但是,odoc 的開發人員不喜歡添加更多標籤的想法,並建議此資訊應在可提升到文件的屬性中傳達。@julow Jules Aguillon 在 #11024 中提出了實作,我對其進行了審查。此變更本身改進了文件中的警示狀態,這已經是一個很大的改進,並且應該允許我們稍後為多核心安全文件達到更好的設計。

初學者級別的多核心駭客

為了參與 OCaml 5.0 的穩定工作,我花了一些時間以最簡單的方式使 Dynlink 函式庫執行緒安全:透過向函式庫新增全域鎖定。這是少數幾種將全域鎖定新增到現有函式庫有意義的情況之一,因為我們同時不希望對 Dynlink 函數的呼叫在效能方面至關重要,並且真的不希望 Dynlink 中的競爭條件損壞整個程式的狀態。

@garrigue Jacques Garrigue

清理變異計算

對許多人來說,類型定義中的變異數計算相當神秘。目前唯一可用的規範是 2013 年 OCaml 會議上的一個簡短摘要,而且實際上並不完整。我和 Takafumi Saikawa 重新審視了這個問題,並提出了一個更清晰的格狀結構和更明確的演算法。這仍然是一個草稿 PR #11018

將反例的類型檢查從 type_pat 中分離出來

自從引入 GADT 以來,type_pat 這個用於類型檢查模式的函式也被用於在窮盡性檢查期間捨棄不可能的情況。此外,它後來被改為傳遞連續風格,以允許回溯,以便檢查類型是否可被實例化。最初,這似乎是一個好主意,允許分解許多程式碼,但後來 type_pat 中添加了新功能,這兩個角色開始分歧。#11027 是另一個草稿 PR,它將它們分離,將 type_pat 還原為直接風格,並添加一個新的 retype_pat 函式,該函式將部分類型化的樹作為輸入。有趣的是,這種反分解實際上將程式碼大小減少了一百多行。

Coqgen

實驗性的 Gallina 產生後端仍在進展中,儘管速度較慢。對於那些感興趣的人,現在有投影片描述它。http://www.math.nagoya-u.ac.jp/~garrigue/papers/coqgen-slides-tpp2021.pdf

@nojb Nicolas Ojeda Bär

我們正利用 5.0 版本的發布來擺脫隨著時間累積的許多雜物

  • #10897:移除 5.0 版本發布之前所有正式標記為已棄用的內容。
  • #10863:移除 <caml/compatibiliy.h> 標頭,其中包含一些沒有 caml_ 前綴的執行階段符號定義,以便相容。
  • #10896:從標準函式庫中移除 StreamGenlexPervasives,以及 **獨立的** bigarray 函式庫(由於 Bigarray 模組已移至標準函式庫,因此不再需要)。
  • #11002:不再在執行階段系統中使用 Begin_roots/End_roots 巨集。這些尚未移除,因為某些外部專案(例如 camlidl)使用它們。
  • #10922, #10923:將棄用屬性新增到一些沒有這些屬性的符號,以便我們可以在未來的版本中移除它們。

除了所有這些春季大掃除之外,還有一個我長期以來錯過的標準函式庫的小改動

  • #10986:新增 Scanf.scanf_optScanf.sscanf_optScanf.bscanf_opt 返回選項的變體。

@dra27 David Allsopp

適用於原生 Windows OCaml 5 的 mingw-w64 連接埠

我在 2018 年完成了 multicore OCaml 的原始連接埠(請參閱 Discuss 文章);該連接埠已重置為 4.10 並在 2020 年夏季更新,以包含原生程式碼支援,但測試故事還沒有完全到位。然而,它在秋季進行了更新,並在提交到 ocaml/ocaml 的主要 PR 之前及時合併!目前,我們僅在 Windows 上支援 mingw-w64 連接埠:OCaml 4.x 在 systhreads 函式庫中手工實作了所有必需的 pthreads 原語,但對於 5.x,我們至少目前正在使用 mingw-w64 專案中的 winpthreads 函式庫。

同時,Cygwin64 連接埠由於一些稍微複雜的原因而大部分損壞,並且 MSVC 連接埠由於缺乏 C11 原子支援以及上述位於 mingw-w64 中的 winpthreads 函式庫而無法正常工作。MSVC 連接埠極不可能為 OCaml 5.0 做好準備,但我已經使用 C++ 原子和 Visual Studio 手動建置 winpthreads 函式庫的版本使其幾乎可以工作,因此 MSVC 連接埠的盡頭還是有曙光的,希望適用於 OCaml 5.1!

適用於 Visual Studio 的 FlexDLL 更新

Windows SDK 的最新變更要求在 flexlink 連結器中進行一些修改,該連結器用於 OCaml 的原生 Windows 連接埠和 Cygwin 連接埠。這個專案獲得了一些合併的關注和發布,因此 Visual Studio 2017 和 2019 以及 Visual Studio 2022 的最新更新現在可以成功建置 OCaml 4.x,即使使用 Windows 11 SDK 也是如此。

4.14 中的原生頂層函式庫

我幫助上游了一些其他人完成的工作,並縮小了 4.14 中 ocamlocamlnat 之間的差異。在 OCaml 4.14 中,原生頂層*函式庫*現在始終安裝,這為希望解釋頂層語句的原生程式碼程式(例如,mdx 工具的原生版本)鋪平了道路。這項工作的一部分還將一些鉤子插入到頂層,允許將外部連結器替換為動態程式碼發射器(所謂的「ocaml-jit」,但使用 *JIT* 似乎會引起很多混淆 - 重點是消除了重複呼叫外部組譯器的開銷,這在編譯大量單獨的短語時是可以測量的)。

glibc 2.34 向後移植

glibc 2.34 包含對訊號處理常式的替代堆疊的配置方式的變更。此問題在 4.13 中已部分修復,但隨著較新的發行版開始隨附此較新的 glibc,較舊 OCaml 版本的行為變更正變得越來越麻煩 - 例如,您無法在最新版本的 Ubuntu 或 Fedora 上安裝 OCaml 4.12 或更早版本。Xavier 處理了 4.13 中已經存在的修復的後半部分(在終止時取消配置替代堆疊),而我管理了將其向後移植到 opam-repository 中所有 OCaml 版本(3.07+!)的過程。我們還決定將這些修補程式推送到 GitHub 上的舊發行分支,部分原因是在那裡方便儲存它們,因為 opam-repository 從那裡引用它們,部分原因是它允許仍然直接從 git clone 編譯較舊的分支。