為 .NET 編譯 Rust — 堆疊解壓、ARM 和 CIL 樹

https://fractalfir.github.io/generated_html/rustc_codegen_clr_v0_1_1.html

Rust 編譯成.NET:堆棧展開、ARM 和 CIL 樹編译 Rust 到.NET 已超過一個月,我取得了相當大的進步!修復了許多錯誤和實現堆棧展開(Rust 處理“panics”的方式)。也有一些架構性變化:重做了代表.NET 組件的方式。rustc_codegen_clr 是一種 Rust 編譯器後端(編譯器模組/插件),允許它從 Rust 代碼生成.NET 組件。CIL 樹 CIL(Common Intermediate Language)實際上是存儲在.NET 組件中的內容,也是我將 Rust 代碼編譯為 CIL 的方法。因此,處理 CIL 的代碼必須健壯且容易擴展。以前,我選擇了一種非常簡單的方式來存儲 CIL。在組件中,操作存儲在連續數組中。它們會從評估堆棧中推送和彈出值。這種格式非常適合節省磁盤空間,但它不存儲對我來說至關重要的一些信息。對於最佳化和驗證 CIL,ops 之間的關係方式至關重要。看看這組 ops 的順序:ldarg.0 ldarg.1 sizeof T mul add ldobj T ret 不會立即明顯知道每個操作作用的值從哪裡來。哪些值被加?哪些是 mul 指令的輸入?使用圖表使這些問題變得易於回答。查看這個圖表,很容易看出每個操作依賴於什麼—這樣就很容易優化這樣的樹。樹還有另一個有趣的特性—僅“樹”的“根”可以寫入任何變量。通過將“stind.i8”(設置 8 字節地址上的整數)或“stloc.n”(設置本地變量 n)等指令限制只出現在樹的根部,我可以非常容易地檢查樹是否可重新排序的。用“CIL 樹”的另一個好處是它們在構造時是經過驗證的。如果我從最後一個示例中刪除了一個指令,這則非常難以看到問題:ldarg.0 ldarg.1 sizeof T mul ldobj T ret 但如果我們使用圖表,就可以清楚地看到這組 CIL 的 ops 序列是無效的。更好的是什麼呢?由於一個樹的節點的編碼方式,不可能使 mul 指令具有 3 個輸入。mul 節點只能引用確切的 2 個其他節點。除了 Call 以外,所有節點都有固定的輸入量和確切的一個輸出。構造它們時不會犯錯誤。回顧過去,我覺得我應該從一開始就採取這種方式。當時,我認為使用“線性”ops 會使代碼庫簡化,但事實證明,增加的複雜性是非常值得的。不能用的 dup 有一個好抽象,但它不能代表 CIL 操作的所有有效組合。有一個指令特別難以在 CIL 中表示:dup。這個指令有一個輸入,但有兩個輸出。你不能用一個簡單的樹來代表包含它的方法。它只能用在有下裝置的地方。這是的,它不再是一個樹,但是誰會在意語義呢?不幸的是,使用 dup 會迫使我考慮評估堆棧上數值的精確順序。這並不像看起來那樣容易。考慮這個數學公式:f(x, y) = (x / y) / x; 在 CIL 中這個表達為: ldarg.x ldarg.y // Divides x by y div // Divides (x/y) by x ldarg.x div 你無法在這裡使用 dup:y 的值需要在兩個 x 之間。所以,如果我要在 CIL 樹中使用 dup,我就必須跟踪評估堆棧。因為避免這些是我放棄線性 CIL 的主要原因之一,我對這樣做不那麼熱衷。(發表部分)

via Hacker News

March 9, 2024 at 10:07AM

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *