
Rust 編譯器 MIR Visitor 指南用 Visitor 與 MutVisitor 遍歷、分析并改寫 MIR【免費下載鏈接】rustEmpowering everyone to build reliable and efficient software.項目地址: https://gitcode.com/GitHub_Trending/ru/rust導讀MIRMid-level Intermediate Representation中間表示是 Rust 編譯器在類型檢查之后、代碼生成之前使用的核心程序表示而 MIR visitor 則是圍繞它提供的一套標準遍歷機制你可以用它掃描Body中的任意元素語句、終結符、局部變量、操作數……來做分析也可以直接原地改寫 MIR。本文將結合當前倉庫compiler/rustc_middle/src/mir/visit.rs與compiler/rustc_mir_transform/src/prettify.rs中的真實實現完整講解Visitor/MutVisitor兩個 trait 的用法、visit_foo與super_foo的協作約定、宏生成原理以及compiler/rustc_middle/src/mir/traversal.rs提供的 CFG 遍歷工具讀完后你將能夠獨立實現一個自定義 MIR 優化或分析 pass。MIR visitor 是什么MIR visitor 是一個用于遍歷 MIR 的便捷工具它既能讓你在遍歷過程中查找感興趣的東西分析也能讓你修改MIR改寫。其核心定義位于compiler/rustc_middle/src/mir/visit.rs的rustc_middle::mir::visit模塊中。該模塊包含兩個visitor trait它們由同一個宏make_mir_visitor!生成見該文件第 68 行的宏定義與第 1276~1277 行的兩次調用Visitor以Bodytcx共享引用為輸入回調時給出共享引用適用于只讀分析MutVisitor以mut Bodytcx可變引用為輸入回調時給出可變引用允許原地改寫 MIR。由于兩者來自同一份宏模板方法簽名與遍歷邏輯完全一致唯一的差別是引用的可變性。源碼模塊注釋compiler/rustc_middle/src/mir/visit.rs頂部明確了三條約定對每一個 MIR 類型都引入一對visit_foo與super_foo方法visit_foo默認調用super_foosuper_foo默認解構foo并逐個調用相應的visit_foo。這套約定與面向對象語言中子類重寫visit方法、再調用super獲得默認行為的模式一致這也是該文件頂部注釋中 Just as in an OO language 的含義所在。如何實現一個自定義 visitor實現 visitor 分兩步。第一步創建一個類型用來表示你的 visitor并讓它持有遍歷 MIR 時所需的狀態例如TyCtxtstruct MyVisitor... { tcx: TyCtxttcx, ... }第二步為該類型實現Visitor或MutVisitortrait。以下面的改寫型 visitor 為例impltcx MutVisitortcx for MyVisitor { fn visit_foo(mut self, ...) { ... self.super_foo(...); } }其中...處寫入你希望在遇到foo元素時執行的邏輯如果還需要繼續遞歸遍歷foo的內部結構就調用對應的super_foo方法。一個關鍵規則是永遠不要重寫super_foo——它承擔的是默認的深度遍歷職責重寫它會破壞遍歷完整性。需要自定義行為時改寫對應的visit_foo然后在方法內調用self.super_foo(...)來回到默認行為。實戰示例LocalFinder 如何找出可重排序的局部變量原文檔給出的入門示例是 [LocalFinder]它通過只實現visit_local這一個方法找出可作為重排候選的局部變量。在當前倉庫中LocalFinder位于compiler/rustc_mir_transform/src/prettify.rs第 115 行起服務于ReorderLocalspass——該 pass 會把局部變量重排成使用順序即首次被賦值或引用的Location越小的局部變量編號越小但不重排函數參數與返回占位RETURN_PLACE。LocalFinder的結構體與狀態struct LocalFinder { map: IndexVecLocal, Local, // 記錄遇到過的 Local按首次出現順序排列 seen: DenseBitSetLocal, // 去重避免同一 Local 被記錄多次 } impl LocalFinder { fn track(mut self, l: Local) { if self.seen.insert(l) { self.map.push(l); } } }它的Visitor實現非常精簡只覆蓋了一個方法impltcx Visitortcx for LocalFinder { fn visit_local(mut self, l: Local, context: PlaceContext, _location: Location) { // 排除非使用場景避免 StorageLive 影響 Local 的排序位置 // 因為 StorageLive 出現的位置并不代表該 Local 真正被賦值的時間。 if context.is_use() { self.track(l); } } }這個例子體現了 visitor 的兩個關鍵點按需覆蓋不需要重寫所有方法只覆蓋visit_local就能攔截遍歷中遇到的每一個Local上下文參數PlaceContext告訴你在當前使用場景下該Local是被讀、被寫還是被用于存儲標記等使你能像上面那樣過濾掉非使用non-use場景。在ReorderLocals::run_pass中visitor 是這樣被驅動的compiler/rustc_mir_transform/src/prettify.rs第 54~73 行先用visit_basic_block_data對每個基本塊做一次遍歷收集順序再補上遍歷中未出現的局部變量例如只出現在調試信息里的最后把map反轉為LocalUpdater使用的置換映射。LocalFinder是一個只讀Visitor的典型范例它只用Body收集信息不修改任何東西。只讀與可變兩個 trait 的協作模式MutVisitor型 visitor 的經典例子是同一文件中的LocalUpdatercompiler/rustc_mir_transform/src/prettify.rs第 138~151 行impltcx MutVisitortcx for LocalUpdatertcx { fn tcx(self) - TyCtxttcx { self.tcx } fn visit_local(mut self, l: mut Local, _: PlaceContext, _: Location) { *l self.map[*l]; } }它與LocalFinder形成了鮮明的對照方面LocalFinder只讀LocalUpdater改寫實現的 traitVisitorMutVisitor輸入Bodytcxmut Bodytcxvisit_local參數l: Local值傳遞l: mut Local可變引用行為收集、記錄信息就地改寫局部變量編號MutVisitor的 trait 要求實現fn tcx(self) - TyCtxttcx方法源碼中LocalUpdater通過字段持有tcx并返回之。這種先只讀分析、再可變改寫的兩階段模式在 MIR 優化中非常常見LocalFinder先計算出置換映射LocalUpdater再把它應用到整個 body。源碼級深入make_mir_visitor 宏與窮盡匹配的設計哲學compiler/rustc_middle/src/mir/visit.rs共有約 1500 行絕大部分代碼由make_mir_visitor!宏模板第 68 行起一次定義、兩次實例化避免了Visitor與MutVisitor兩套代碼的重復維護。宏通過$($mutability)?參數把是否有mut注入到所有簽名中macro_rules! make_mir_visitor { ($visitor_trait_name:ident, $($mutability:ident)?) { ... } } make_mir_visitor!(Visitor,); // 無 mut只讀 make_mir_visitor!(MutVisitor, mut); // 帶 mut可變宏模板中還嵌套了extra_body_methods!第 1035 行起為MutVisitor額外生成visit_body_preserves_cfg等方法。從源碼結構看這些方法專門服務于不改動 CFG 結構的改寫型 pass——例如ReorderLocals就調用updater.visit_body_preserves_cfg(body)來重寫整個 body 中的Local編號同時保證基本塊之間的跳轉結構不被破壞。另一個值得關注的設計細節是super_basic_block_data的實現compiler/rustc_middle/src/mir/visit.rs第 296~322 行fn super_basic_block_data( mut self, block: BasicBlock, data: $($mutability)? BasicBlockDatatcx ) { let BasicBlockData { statements, after_last_stmt_debuginfos, terminator, is_cleanup: _ } data; // 依次以遞增的 statement_index 構造 Location訪問每條語句…… // 之后訪問 after_last_stmt_debuginfos 與 terminator }這里刻意使用let BasicBlockData { ... } data的窮盡模式匹配來解構字段而不是在方法體里寫data.statements。這樣做的目的源碼注釋明確說明是一旦有人給BasicBlockData增加了新字段編譯器就會強制要求修訂這段代碼從而但愿調用上正確的新增 visit 方法。因此該文件的維護約定非常嚴格——所有 match 必須在字段和變體上窮盡既不能用..跳過字段也不能用_跳過變體只有確實無需訪問的字段如is_cleanup才允許用_匹配。同時遍歷中為每條語句構造的Location { block, statement_index }也是 MIR 中定位語句的唯一坐標visit_statement/visit_terminator都會攜帶它。常用 visit 方法速覽Visitor/MutVisitortrait 中提供了覆蓋 MIR 各層結構的方法核心的包括visit_body/visit_basic_block_data遍歷整個函數體及其基本塊visit_statement/visit_terminator遍歷語句與終結符是絕大多數 pass 的切入點visit_assign/visit_rvalue/visit_operand深入賦值語句、右值與操作數visit_place/visit_local訪問 place存儲位置與局部變量其中visit_place額外攜帶PlaceContext說明使用方式visit_const_operand/visit_ty/visit_span等訪問常量、類型與源碼位置span。從super_terminator的實現第 516 行起可以看到終結符各變體的默認遍歷行為Return會以NonMutatingUseContext::Move訪問返回占位RETURN_PLACE且MutVisitor下斷言它不可被改寫SwitchInt訪問判別值操作數Call/TailCall依次訪問函數操作數、實參操作數并標記目的 placeDrop以MutatingUseContext::Drop訪問被 drop 的 place。需要針對特定終結符做分析或改寫時重寫visit_terminator并匹配TerminatorKind即可。MIR CFG 的遍歷工具traversal 模塊除了逐元素遍歷的 visitorcompiler/rustc_middle/src/mir/traversal.rs提供了按控制流圖CFG順序遍歷基本塊的函數適合需要全局控制流視角的 passpreorder前序遍歷。每個節點在其某個前驅被訪問之后才被訪問等價于對 CFG 做深度優先搜索并按發現時間返回節點從START_BLOCK出發利用DenseBitSet去重、Vec作為工作棧第 22~76 行。postorder后序遍歷。每個節點在其所有后繼回邊可達的后繼除外被訪問之后才被訪問等價于按完成時間返回實現使用visit_stack棧維護(基本塊, 后繼迭代器)并借助traverse_successor不斷下探第 97~211 行。reverse_postorder后序遍歷的反轉由postorder(body).rev()直接得到第 221~226 行這是許多數據流分析偏好的順序。mono_reachable_reverse_postorder單態化版本的后序反轉遍歷需要TyCtxt與Instance以便精確計算終結符的后繼第 228~240 行適用于需要區分調用目標的具體實例monomorphized場景。文檔中的示例圖說明了兩種順序的差異對一個從A分叉到B、C再匯合到D的圖前序遍歷結果為A B D C或A C D B后序遍歷結果為D B C A或D C B A。選擇哪種順序取決于你的分析對依賴方向的要求例如反向數據流分析通常需要前序正向數據流分析則常用后序或反后序。何時用 Visitor何時用 traversal兩者服務于不同層級的需求Visitor面向 MIR 的樹狀/嵌套結構無論元素位于哪個基本塊只要在遍歷路徑上就會被回調適合找出所有Local改寫所有Operand這類與位置無關的操作traversal面向 MIR 的圖狀結構CFG它只按基本塊為單位給出順序適合數據流分析、塊級順序敏感的處理。實際優化 pass 常將兩者結合用traversal決定基本塊的訪問順序用Visitor或直接按塊索引處理塊內細節而像LocalFinder這樣的場景則用visit_basic_block_data一次性遍歷整個 body 并在回調中按需篩選。理解這兩套工具的分工是編寫高質量 MIR pass如rustc_mir_transform中的各類優化、以及編譯器自身的各種分析的基礎。【免費下載鏈接】rustEmpowering everyone to build reliable and efficient software.項目地址: https://gitcode.com/GitHub_Trending/ru/rust創作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考