* 參考書籍: * [[https://www.amazon.com/Getting-Started-LLVM-Core-Libraries/dp/1782166920|Getting Started with LLVM Core Libraries]] * [[https://www.amazon.com/LLVM-Essentials-Suyog-Sarda/dp/1785280805|LLVM Essentials]] * 編譯器前端 Clang 的[[clang|維基]]。 * 精簡提煉,儘量不描述細節。可以搭配命令行觀察結果更佳。 * 開頭附註 LLVM 官方文檔。 * 使用 gdb 時,可以透過 dump() 印出訊息。 * 善用 [[https://llvm.org/viewvc/llvm-project/|ViewVC]] 查閱相關功能的改動。 待整理: * [[http://lists.llvm.org/pipermail/llvm-dev/2017-November/118810.html|[llvm-dev] What pattern string corresponds to CopyToReg?]] 官方文檔: * [[http://www.llvm.org/docs/Lexicon.html|The LLVM Lexicon]]: LLVM 術語 * [[http://www.llvm.org/docs/ProgrammersManual.html|LLVM Programmer’s Manual]]: 必讀。 * [[http://www.llvm.org/docs/tutorial/|LLVM Tutorial]] 線上文章: * [[http://www.aosabook.org/en/llvm.html|Chapter 11. LLVM]] * [[http://www.informit.com/articles/article.aspx?p=1215438|How the LLVM Compiler Infrastructure Works]] * [[https://wwwcip.informatik.uni-erlangen.de/~sicherha/foo/tricore-llvm.pdf|Design and Implementation of a TriCore Backend for the LLVM Compiler Framework (PDF)]] * [[http://reup.dmcs.pl/wiki/images/7/7a/Tricore-llvm-slides.pdf|Design and Implementation of a TriCore Backend for the LLVM Compiler Framework (Slide)]] * [[http://scholarworks.rit.edu/theses/9550/|The Design of a Custom 32-bit RISC CPU and LLVM Compiler Backend]] * [[http://elinux.org/images/b/b7/LLVM-ELC2009.pdf|Understanding and writing an LLVM compiler back-end]] * [[https://www.slideshare.net/WeiRenChen/mclinker2013chenwj|Part II: LLVM Intermediate Representation]] * [[https://www.slideshare.net/zongfanyang/coscup-2016-llvm|COSCUP 2016 - LLVM 由淺入淺]] 線上課程: * [[http://www.cl.cam.ac.uk/teaching/1617/L25/|Modern Compiler Design]] 開發注意事項: * O2 優化的 bug。基本透過裁減代碼,得到最小的測試用例以定位到問題點。目前的經驗是裁減到函式級別,隨即對該函式的匯編進行除錯。 * 測試套儘早運行,及早抓到 bug。 * 開發時如果沒有 debugger,需要透過 printf 打印訊息。但是要注意 printf 是否能正確執行,另外 printf 可能會影響 O2 代碼生成,請謹慎使用。 printf("%8x %8x %8x %8x", *((unsigned int *)(&var) + 0), *((unsigned int *)(&var) + 1), *((unsigned int *)(&var) + 2), *((unsigned int *)(&var) + 3)); * 針對目標函式,打印 IR 的一連串變換 (LLVM IR -> DAG -> Machine IR -> MCInstr),觀察何時產生問題代碼。 $ clang -O2 -c -mllvm -print-after-all -mllvm -filter-print-funcs=main func.c * 如果是從 GCC 移植到 LLVM,可能有有限的 debugger。必要時,對比 O0 和 O2 的匯編,觀察暫存器和內存的值,再反推源代碼出錯的位址,進而推導優化過程中的問題。匯編和源代碼的映射必須基本理解,以利反推。 * 關於 O2 代碼生成質量,盡可能從 LLVM IR,SelectionDAG 和 TableGen 方向上優化,再考慮其它作法 (如 peephole)。 * 參考其他後端時,從 LLVM IR 開始對比。即使只有細微差異,都可能會影響後續優化效果。 * LLVM IR $ clang -O0 -emit-llvm -S -o sum.ll sum.c # 和參考後端比較 O2 IR 生成過程中是否有差異。 $ opt -O2 sum.ll -print-after-all * SelectionDAG $ clang -O2 -emit-llvm -S -o sum.ll sum.c # 觀察 DAG 變換過程,看是否有優化空間。 $ llc sum.ll -debug * 優化/減少 load/store。 * [[http://lists.llvm.org/pipermail/llvm-dev/2015-April/084155.html|[LLVMdev] How to enable use of 64bit load/store for 32bit architecture]] ====== 編譯 LLVM ====== * [[http://llvm.org/docs/GettingStarted.html|Getting Started with the LLVM System]] * [[https://ninja-build.org/|Ninja, a small build system with a focus on speed]] $ brew install cmake ninja $ git clone http://llvm.org/git/llvm.git $ git clone http://llvm.org/git/clang.git llvm/tools/clang $ mkdir build.Ninja; cd build.Ninja $ cmake -G Ninja -DCMAKE_INSTALL_PREFIX=/Users/chenwj/Projects/opt/ -DCMAKE_BUILD_TYPE=Debug ../llvm $ ninja; ninja install * [[http://stackoverflow.com/questions/17980759/xcode-select-active-developer-directory-error|xcode-select active developer directory error]] $ mkdir build.Xcode $ cmake -G Xcode -DCMAKE_INSTALL_PREFIX=/Users/chenwj/Projects/opt/ -DCMAKE_BUILD_TYPE=Debug ../llvm $ open LLVM.xcodeproj * 或是點擊 LLVM.xcodeproj/project.pbxproj,開啟 Xcode。 * 選擇手動建立 Scheme,目前只選 clang。 * [[https://heejune.me/2016/08/09/wheres-my-llvmtoolsclangexample-binaries/|Where’s my ‘llvm/tools/clang/example’ binaries?]] $ cd build.Ninja $ cmake -G Ninja -DCMAKE_INSTALL_PREFIX=/Users/chenwj/Projects/opt/ -DCMAKE_BUILD_TYPE=Debug -DLLVM_BUILD_EXAMPLES=1 ../llvm # 編譯 example BrainF $ ninja BrainF ====== 使用範例 ====== * -help-hidden 可以顯示更多的選項。 * -debug-only= 可以只顯示屬於特定 DEBUG_TYPE 的資訊。 * grep DEBUG_TYPE lib/* | grep pre-RA-sched 可以列出屬於同一類 DEBUG_TYPE 的檔案。 * -filter-view-dags= 可以只顯示特定 basic block 的 DAG 資訊。 * [[http://llvm.org/docs/CommandGuide/index.html|LLVM Command Guide]] * 輸出 LLVM IR。-S 輸文字格式 LLVM IR (.ll),-c 輸出二進制格式 LLVM IR (.bc)。 $ clang -emit-llvm -S hello.c -o hello.ll $ clang -emit-llvm -c hello.c -o hello.bc * 透過 Clang,傳命令行參數給 LLVM 後端。 $ clang -c sum.c -mllvm -debug-pass=Structure * 匯編和反匯編 LLVM IR。匯編將 .ll 轉成 .bc; 反匯編將 .bc 轉成 .ll。 $ llvm-as hello.ll -o hello.bc $ llvm-dis hello.bc -o hello.ll * 如果想要使用 LLVM 的標頭檔和函式庫。 $ gcc `llvm-config --cxxflags --ldflags` foo.c `llvm-config --libs` * 將 LLVM IR 編譯成目標平台匯編或目的檔。 # 顯示隱藏選項。 $ llc -help-hidden # 顯示在編譯過程中傳遞哪些參數給 opt。 $ llc -debug-pass=Arguments sum.ll # 顯示在編譯過程中 PassManager 調度哪些 pass。 $ llc -debug-pass=Structure sum.ll # 只顯示代碼中有 #define DEBUG_TYPE "legalize-types" 的 pass。 $ llc -debug-only=legalize-types sum.ll * 優化 LLVM IR。 # 顯示隱藏選項。 $ opt -help-hidden # 顯示每一個 pass 處理之後的 LLVM IR。可以用來觀察感興趣的 pass 做了哪些變換。 $ opt -S -O2 -print-after-all sum.ll ====== LLVM IR ====== * [[http://llvm.org/docs/LangRef.html|LLVM Language Reference Manual]] * LLVM IR 並非平台中立。請見 * [[http://www.llvm.org/docs/FAQ.html#can-i-compile-c-or-c-code-to-platform-independent-llvm-bitcode|Can I compile C or C++ code to platform-independent LLVM bitcode?]] * LLVM IR 作為編譯器的中間語言,不需要像 Java bytecode 那樣考慮平台中立性,設計上可以往方便後端優化考慮。 * [[http://llvm.org/devmtg/2011-09-16/EuroLLVM2011-MoreTargetIndependentLLVMBitcode.pdf|More Target Independent LLVM Bitcode]] * [[http://lists.llvm.org/pipermail/llvm-dev/2011-October/043777.html|[LLVMdev] LLVM IR is a compiler IR]] * 對應 LLVM [[http://llvm.org/doxygen/Instruction_8h_source.html|Instruction]] $ cat sum.c int sum(int a, int b) { return a + b; } $ clang -emit-llvm -S sum.c -o sum.ll ; ModuleID = 'sum.c' source_filename = "sum.c" ; 從底下 target datalayout 和 triple 即可知 LLVM IR 並非平台中立。 target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-apple-macosx10.11.0" ; Function Attrs: noinline nounwind ssp uwtable define i32 @sum(i32 %a, i32 %b) #0 { entry: %a.addr = alloca i32, align 4 ; 在 stack 上分配空間給 i32,4 byte 對齊。 %b.addr = alloca i32, align 4 store i32 %a, i32* %a.addr, align 4 ; 將函式參數存到 stack。 store i32 %b, i32* %b.addr, align 4 %0 = load i32, i32* %a.addr, align 4 ; 從 stack 讀出 operand。 %1 = load i32, i32* %b.addr, align 4 %add = add nsw i32 %0, %1 ; 加法運算。 ret i32 %add ; 返回運算結果。 } * [[http://llvm.org/docs/LangRef.html#data-layout|Data Layout]]: 有些優化會查詢 data layout,請注意設置是否正確。 * [[http://llvm.org/docs/LangRef.html#target-triple|Target Triple]]: ARCHITECTURE-VENDOR-OPERATING_SYSTEM 跟 LLVM IR 相關的類如下,所在目錄為 [[http://llvm.org/doxygen/dir_c3e93f23a4a31c717998b98ce143b7c0.html|include/llvm/IR]]: * [[http://www.llvm.org/docs/ProgrammersManual.html#the-module-class|Module]]: Module 對應到一個 C 檔案。Module 包含多個 Function。 * [[http://www.llvm.org/docs/ProgrammersManual.html#the-function-class|Function]]: Function 對應到一個 C 函式。Function 包含多個 BasicBlock。 * [[http://www.llvm.org/docs/ProgrammersManual.html#the-basicblock-class|BasicBlock]]: BasicBlock 包含一段 single entry single exit 的 Instruction。 * [[http://www.llvm.org/docs/ProgrammersManual.html#the-instruction-class|Instruction]]: 是所有 LLVM IR 的基類。 * [[http://www.llvm.org/docs/ProgrammersManual.html#the-value-class|Value]]: 產生 (define) 值 (Value) 的類均繼承 Value。Value 代表可以被 Instruction 使用,有型別的值 (typed value)。注意!BasicBlock 也是 Value (即為 Value 的子類),因為 BasicBlock 可以作為分支指令的目標。 * 一個 Value 被定義 (define) 之後,可以被多次使用 (use)。Value 維護一個 User 列表,紀錄誰使用到該 Value。提供接口取得 def-use chain 中的 use。 * [[http://www.llvm.org/docs/ProgrammersManual.html#the-user-class|User]]: 使用 (use) 值 (Value) 的類均繼承 User。User 維護一個 operand 列表,代表 User 用到了哪些 Value。 * 每個 operand 會指向它所參考的 Value。因為 LLVM IR 是 SSA,這種參考關係是唯一。提供接口取得 use-def chain 中的 def。 較難理解的部分: * [[http://llvm.org/docs/LangRef.html#getelementptr-instruction|getelementptr]] * [[http://llvm.org/docs/GetElementPtr.html|The Often Misunderstood GEP Instruction]] * 為 [[http://llvm.org/docs/LangRef.html#t-aggregate|aggregate type]] (LLVM 的 array 和 struct) 的子元素計算位址,不涉及內存存取。 $ cat struct.c struct RT { char A; char C; }; char *func1(struct RT *s) { return &(s->A); } char *func2(struct RT *s) { return &(s->C); } char *func3(struct RT *s) { return &(s[1].C); } $ clang -emit-llvm -O1 -S struct.c -o struct.ll %struct.RT = type { i8, i8 } ; Function Attrs: norecurse nounwind readnone ssp uwtable define i8* @func1(%struct.RT* readnone %s) local_unnamed_addr #0 { entry: %A = getelementptr inbounds %struct.RT, %struct.RT* %s, i64 0, i32 0 ret i8* %A } ; Function Attrs: norecurse nounwind readnone ssp uwtable define i8* @func2(%struct.RT* readnone %s) local_unnamed_addr #0 { entry: %C = getelementptr inbounds %struct.RT, %struct.RT* %s, i64 0, i32 1 ret i8* %C } ; Function Attrs: norecurse nounwind readnone ssp uwtable define i8* @func3(%struct.RT* readnone %s) local_unnamed_addr #0 { entry: %C = getelementptr inbounds %struct.RT, %struct.RT* %s, i64 1, i32 1 ret i8* %C } * 第一個參數是 ''%struct.RT'': 第二個參數所指類型。 * 第二個參數是 ''%struct.RT* %0'': 計算位址的起始位址,其型別一定是指針類型。之後的參數皆是欲對其計算位址的子元素的 index。 * 第三個參數是 ''i32 0'': 類似 ''%0[0]''。LLVM 某種程度上將第二個參數視為 array。如果實際上不是 array,此值皆為 0。 * 第四個參數是 ''i32 0'': 類似 ''%0[0].A''。 * [[http://llvm.org/docs/LangRef.html#undefined-values|Undefined Values]]: 指令中帶有 undefined value,代表指令對該值為何不感興趣 (undefined value 可以代表任意的值[(http://lists.llvm.org/pipermail/llvm-dev/2017-June/114489.html)])。編譯器對此類情況有機會可以優化。 * [[http://llvm.org/docs/LangRef.html#poison-values|Poison Values]]: 指令中帶有 poison value,其結果為 undefined behavior。 ===== InstCombine ===== * [[http://llvm.org/doxygen/InstructionSimplify_8cpp_source.html|InstructionSimplify.cpp]] 也負責簡化 LLVM IR。和 InstCombine 不同的地方在於只做運算元折疊,不新建指令。 * [[http://llvm.org/doxygen/classllvm_1_1InstCombiner.html|InstCombine]] 類似於 SelectionDAG 中的 [[http://llvm.org/doxygen/DAGCombiner_8cpp.html|DAGCombiner]],只不過一個作用於 LLVM IR,另一個作用於 SelectionDAG,目的都是簡化 IR/SelectionDAG。 * [[http://llvm.org/doxygen/classllvm_1_1InstCombinePass.html#aa1ffc7ad08181545bbc25eabac7f5aae|InstCombinePass::run]] * [[http://llvm.org/doxygen/classllvm_1_1InstCombiner.html#aded7582678e6359830469d17c727b04e|InstCombiner::run()]] bool InstCombiner::run() { if (Instruction *Result = visit(*I)) { ++NumCombined; // Should we replace the old instruction with a new one? if (Result != I) { DEBUG(dbgs() << "IC: Old = " << *I << '\n' << " New = " << *Result << '\n'); } } * [[http://llvm.org/doxygen/classllvm_1_1InstCombiner.html#a7d92fd2831240f33fba5b54ec91df58d|InstCombiner::visitTrunc()]] ===== Example ===== * Bitfield ([[https://www.tutorialspoint.com/cprogramming/c_bit_fields.htm|C - Bit Fields]]) $ cat bitfield.c typedef struct { unsigned int width : 16; unsigned int height : 16; } status; void foo() { status s; s.width = 4; s.height = 5; } $ clang bitfiled.c -emit-llvm -S -o bitfiled.ll %struct.status = type { i32 } ; Function Attrs: noinline nounwind ssp uwtable define void @foo() #0 { entry: %s = alloca %struct.status, align 4 %0 = bitcast %struct.status* %s to i32* %bf.load = load i32, i32* %0, align 4 %bf.clear = and i32 %bf.load, -65536 ; and FFFF0000。0000 代表先被 clear,之後要被設置的部分,取 status.width。 %bf.set = or i32 %bf.clear, 4 ; or 0004。把 status.width 設為 4。 store i32 %bf.set, i32* %0, align 4 %1 = bitcast %struct.status* %s to i32* %bf.load1 = load i32, i32* %1, align 4 %bf.clear2 = and i32 %bf.load1, 65535 ; and 0000FFFF。0000 代表先被 clear,之後要被設置的部分,取 status.height。 %bf.set3 = or i32 %bf.clear2, 327680 ; or 50000。把 status.height 設為 5。 store i32 %bf.set3, i32* %1, align 4 ret void } * 先 and 把要設置的部分 clear,再對其做 or 把值設進去。 * [[https://clang.llvm.org/doxygen/classclang_1_1CodeGen_1_1CodeGenFunction.html#a35cce91120c88f4809f5bb323acf6851|EmitStoreThroughBitfieldLValue]] ====== MachineInstr ====== * [[https://llvm.org/docs/CodeGenerator.html#machine-code-description-classes|Machine code description classes]] * [[https://llvm.org/docs/MIRLangRef.html|Machine IR (MIR) Format Reference Manual]] * [[https://www.youtube.com/watch?v=objxlZg01D0|Welcome to the back-end: The LLVM machine representation (2017)]] 術語: * MI ([[http://llvm.org/doxygen/classllvm_1_1MachineInstr.html|MachineInstr]]) * 和 LLVM IR 一樣,MI 屬於 [[http://llvm.org/doxygen/classllvm_1_1MachineBasicBlock.html|MachineBasicBlock]]; MachineBasicBlock 屬於 [[http://llvm.org/doxygen/classllvm_1_1MachineFunction.html|MachineFunction]]。 * [[http://llvm.org/doxygen/classllvm_1_1MachineOperand.html|MachineOperand]]: 此條 MI 使用的操作數。 * [[http://llvm.org/doxygen/classllvm_1_1MCInstrDesc.html|MCInstrDesc]]: 描述此條 MI 相關屬性。 * MRI ([[http://llvm.org/doxygen/classllvm_1_1MachineRegisterInfo.html|MachineRegisterInfo]]) * 用來創建虛擬或物理暫存器,並追蹤其相關訊息。 * TII ([[http://llvm.org/docs/doxygen/html/classllvm_1_1TargetInstrInfo.html|TargetInstrInfo]]) * MI 所保存的 Opcode 需要透過目標平台的 TargetInstrInfo 解釋才能得到對應的指令。[[https://llvm.org/docs/CodeGenerator.html#create-instructions|BuildMI]] 創建 MI 時,需要 TII。 * ''llc -print-machineinstrs'' 列出指令選擇後,各個階段的 MachineInstr。 $ llc -print-machineinstrs sum.ll # After Instruction Selection: # 打印出 MachineFunction 的 Property。從 IsSSA 和 TracksLiveness 可以知道我們現在處於暫存器分配前的階段。 # Machine code for function sum: IsSSA, TracksLiveness Frame Objects: fi#0: size=4, align=4, at location [SP+8] fi#1: size=4, align=4, at location [SP+8] Function Live Ins: %EDI in %vreg0, %ESI in %vreg1 # MachineBasicBlock 可以連結到相關的 LLVM IR BasicBlock。 BB#0: derived from LLVM BB %entry Live Ins: %EDI %ESI %vreg1 = COPY %ESI; GR32:%vreg1 %vreg0 = COPY %EDI; GR32:%vreg0 MOV32mr , 1, %noreg, 0, %noreg, %vreg0; mem:ST4[%a.addr] GR32:%vreg0 MOV32mr , 1, %noreg, 0, %noreg, %vreg1; mem:ST4[%b.addr] GR32:%vreg1 %vreg2 = ADD32rm %vreg1, , 1, %noreg, 0, %noreg, %EFLAGS; mem:LD4[%a.addr](dereferenceable) GR32:%vreg2,%vreg1 %EAX = COPY %vreg2; GR32:%vreg2 RET 0, %EAX * ''%vreg'' 是虛擬暫存器,''%ESI'' 是物理暫存器。 * '''' 表示該暫存器於該指令被定義,沒有 '''' 代表該暫存器於該指令被使用。 * '''' 代表被定義的暫存器在該指令之後沒有被使用。 '''' 代表該指令是最後一條使用該暫存器的指令。 * ''imp-def'' 和 ''imp-use'' 表示該暫存器是被隱式定義或使用,不出現在匯編指令中,如 ''%EFLAGS''。 * 對於同時使用和定義的暫存器,以 ''tied'' 表示。其餘 flag 的意義請見 [[http://llvm.org/doxygen/classllvm_1_1MachineOperand.html|MachineOperand]]。 * '''' 表示存取虛擬棧上第幾個 index 的位置。在經過 [[https://llvm.org/doxygen/PrologEpilogInserter_8cpp_source.html|PrologEpilogInserter.cpp]] 之後,'''' 會轉換成目標平台的棧指針計算。 * 棧指針計算過程中,可能因為立即數超過範圍,必須將中間結果存在臨時的暫存器。但注意,[[https://llvm.org/doxygen/PrologEpilogInserter_8cpp_source.html|PrologEpilogInserter.cpp]] 發生在暫存器分配之後,此時需要 [[http://llvm.org/doxygen/RegisterScavenging_8cpp_source.html|RegisterScavenging.cpp]] 清出可用的暫存器。 * 對於 VLIW 架構,[[https://llvm.org/docs/CodeGenerator.html#machineinstr-bundles|MachineInstr Bundles]] 需要了解。 * 針對 MI pass 的測試。 $ llc -stop-after=isel sum.ll -o sum.mir $ llc -run-pass=machine-scheduler sum.mir -o sum_scheduled.mir $ llc -start-after=machine-scheduler sum_scheduled.mir -o sum.s $ llc -verify-machineinstrs sum.ll ====== MachineCode ====== 從匯編器角度所設計的 [[http://llvm.org/doxygen/MCInst_8h_source.html|MCInst]] (MachineCode),所帶的資訊比 [[http://llvm.org/doxygen/classllvm_1_1MachineInstr.html|MachineInstr]] (MI) 更少,其主要用途是讓匯編器生成目的檔,或是透過反匯編器生成 MCInst。 {{http://image1.slideserve.com/2196423/slide4-n.jpg?500}} 相關文章: * [[http://blog.llvm.org/2010/04/intro-to-llvm-mc-project.html|Intro to the LLVM MC Project]] * [[http://llvm.org/devmtg/2011-11/Grosbach_Anderson_LLVMMC.pdf|LLVM MC in Practice]] * [[http://www.embecosm.com/appnotes/ean10/ean10-howto-llvmas-1.0.html|Howto: Implementing LLVM Integrated Assembler]] # 產生匯編的同時,於註解加上對應的 MCInst。 $ llc -asm-show-inst sum.ll -o - movq %rsp, %rbp ## ## > # 產生匯編的同時,於註解加上對應的指令編碼。 $ llc -show-mc-encoding sum.ll movq %rsp, %rbp ## encoding: [0x48,0x89,0xe5] # llvm-mc 用於測試 MCInst 的匯編和反匯編。 $ echo "movq%rsp, %rbp" | llvm-mc -show-encoding .section __TEXT,__text,regular,pure_instructions movq %rsp, %rbp ## encoding: [0x48,0x89,0xe5] $ echo "0x48,0x89,0xe5" | llvm-mc -disassemble .section __TEXT,__text,regular,pure_instructions movq %rsp, %rbp $ echo "0x48,0x89,0xe5" | llvm-mc -disassemble -show-inst .section __TEXT,__text,regular,pure_instructions movq %rsp, %rbp ## ## > 在 post-register allocation pass 之後,於 [[http://llvm.org/doxygen/classllvm_1_1AsmPrinter.html|AsmPrinter]] 調用 EmitInstruction,將 MI 轉換成 MCInst,可以輸出匯編或是目的檔。目標平台繼承 AsmPrinter 並覆寫相關函式。 {{https://image.slidesharecdn.com/mclinker-2013-chenwj-160719142821/95/part-ii-llvm-intermediate-representation-29-638.jpg?500}} * [[http://llvm.org/doxygen/classllvm_1_1X86AsmPrinter.html#a6df9e42fd7764d9b3dd03c187b0ebd1e|X86AsmPrinter::runOnMachineFunction()]] * [[http://llvm.org/doxygen/classllvm_1_1X86AsmPrinter.html#a5898a2d4fd90468e41f05af8e5e7fa6a|X86AsmPrinter::EmitInstruction ()]] * 在這過程中,透過 [[http://llvm.org/doxygen/X86MCInstLower_8cpp_source.html|X86MCInstLower]] 將 MI 轉成 MCInst。 $ lldb llc (lldb) b X86AsmPrinter::runOnMachineFunction # MCAsmStreamer (lldb) r -filetype=asm sum.ll # MCObjectStreamer (lldb) r -filetype=obj sum.ll ======= Pass ======= * [[http://llvm.org/docs/Passes.html|LLVM's Analysis and Transform Passes]] * Pass 主要可以分為兩類,[[http://llvm.org/docs/Passes.html#analysis-passes|Analysis Passes]] 和 [[http://llvm.org/docs/Passes.html#transform-passes|Transform Passes]]。Transform 依賴 Analysis 的結果。[[http://llvm.org/doxygen/PassManager_8h_source.html|Pass Manager]] 根據 Pass 之間的依賴關係執行調度。比如要先執行 use analysis 再執行 dead code elimination。 * 可以用 ''opt'' 在 LLVM IR 上運行各種 Pass,如: [[http://llvm.org/docs/Passes.html#mem2reg-promote-memory-to-register|mem2reg]] 。 # 對 sum.ll 運行 mem2reg Pass,再將其產生的 LLVM bitcode 反組譯成 LLVM IR。 $ opt -mem2reg sum.ll | llvm-dis ; Function Attrs: noinline nounwind ssp uwtable define i32 @sum(i32 %a, i32 %b) #0 { entry: %add = add nsw i32 %a, %b ; 可以觀察到 alloca 被消除。 ret i32 %add } * 加入自己的 Pass 請見 [[http://llvm.org/docs/WritingAnLLVMPass.html|Writing an LLVM Pass]] $ opt -load=install/lib/libLLVMHello.so -hello < hello.bc > /dev/null * 如果出現底下錯誤訊息,請確定 ''opt'' 和 ''libLLVMHello.so'' 是同一份源碼和設定編譯而成。 Error opening 'install/lib/libLLVMHello.so': install/lib/libLLVMHello.so: undefined symbol: _ZN4llvm4Pass26getAdjustedAnalysisPointerEPKv -load request ignored. * 後端比較常見的是 [[http://llvm.org/doxygen/classllvm_1_1MachineFunctionPass.html|MachineFunctionPass]] * [[http://llvm.org/docs/WritingAnLLVMPass.html#the-machinefunctionpass-class|The MachineFunctionPass class]] * Pass 透過實現 ''getAnalysisUsage(AnalysisUsage &AU)'' 指定其輸出入的依賴關係。''AU.addRequired()'' 指定當前 pass 需要哪些 PassName 先運行; ''AU.addPreserved()'' 指定當前 pass 運行之後,PassName 的結果不受影響,可以繼續使用。 * [[http://llvm.org/docs/WritingAnLLVMPass.html#specifying-interactions-between-passes|Specifying interactions between passes]] * [[http://llvm.org/doxygen/classllvm_1_1PassManager.html|PassManager]] * [[http://llvm.org/devmtg/2007-05/03-Patel-Passmanager.pdf|Demystifying The LLVM Pass Manager]] 描述[[http://llvm.org/doxygen/classllvm_1_1legacy_1_1PassManagerBase.html|舊版 Pass Manager]]。 * [[http://llvm.org/devmtg/2014-04/PDFs/Talks/Passes.pdf|Passes in LLVM (2014/04)]] ([[https://www.youtube.com/watch?v=rY02LT08-J8|Video]]) 和 [[http://llvm.org/devmtg/2014-10/Slides/Carruth-TheLLVMPassManagerPart2.pdf|The LLVM PassManager Part 2 (2014/10)]] ([[http://llvm.org/devmtg/2014-10/Videos/The%20LLVM%20Pass%20Manager%20Part%202-720.mov|Video]]) 描述 [[http://llvm.org/doxygen/classllvm_1_1PassManagerBuilder.html|PassManagerBuilder]]。 * PassManagerBuilder 根據優化等級建立 pass pipeline。以 [[https://github.com/llvm-mirror/llvm/blob/master/tools/opt/opt.cpp|opt]] 為例: static void AddOptimizationPasses(legacy::PassManagerBase &MPM, legacy::FunctionPassManager &FPM, TargetMachine *TM, unsigned OptLevel, unsigned SizeLevel) { if (!NoVerify || VerifyEach) FPM.add(createVerifierPass()); // Verify that input is correct PassManagerBuilder Builder; Builder.OptLevel = OptLevel; Builder.SizeLevel = SizeLevel; if (DisableInline) { // No inlining pass } else if (OptLevel > 1) { Builder.Inliner = createFunctionInliningPass(OptLevel, SizeLevel, false); } else { Builder.Inliner = createAlwaysInlinerLegacyPass(); } Builder.populateFunctionPassManager(FPM); Builder.populateModulePassManager(MPM); } * PassManagerBuilder 透過 [[http://llvm.org/doxygen/classllvm_1_1PassManagerBuilder.html#a4481c1ec64d03d8e75179e30205a8b0e|populateFunctionPassManager]] 和 [[http://llvm.org/doxygen/classllvm_1_1PassManagerBuilder.html#a5e9bd6b778471eabca960496422421b3|populateModulePassManager]] 對舊有的 [[http://llvm.org/doxygen/classllvm_1_1legacy_1_1FunctionPassManager.html|FunctionPassManager]] 和 ModulePassManager (其實是 PassManagerBase?) 添加 pass,組成 pass pipeline。 * 舊版 PassManager 透過繼承,決定是 Function 還是 Module PassManager。{{http://llvm.org/doxygen/classllvm_1_1legacy_1_1PassManagerBase__inherit__graph.png}} * 新版 PassManager 透過模板參數,決定是 Function 還是 Module PassManager。 extern template class PassManager; typedef PassManager ModulePassManager; extern template class PassManager; typedef PassManager FunctionPassManager * [[http://llvm.org/doxygen/classllvm_1_1Pass.html|Pass]] * 以 [[http://llvm.org/doxygen/PreISelIntrinsicLowering_8cpp_source.html|PreISelIntrinsicLowering.cpp]] 為例: * PreISelIntrinsicLoweringLegacyPass 是舊版 pass,繼承 [[http://llvm.org/doxygen/classllvm_1_1ModulePass.html|ModulePass]]。注意,這些 LegacyPass 放在 anonymous namespace,只在檔案內部可見。 * [[http://llvm.org/doxygen/structllvm_1_1PreISelIntrinsicLoweringPass.html|PreISelIntrinsicLoweringPass]] 是新版 pass,可以看到不再繼承 ModulePass。 * [[http://llvm.org/doxygen/classllvm_1_1TargetPassConfig.html|TargetPassConfig]] * 目標平台在 TargetMachine.cpp 實現 TargetPassConfig,可以將目標平台所需的 pass 添加進 pipeline。 ====== Code Generator ====== * [[http://llvm.org/docs/CodeGenerator.html|The LLVM Target-Independent Code Generator]] * [[http://eli.thegreenplace.net/2012/11/24/life-of-an-instruction-in-llvm|Life of an instruction in LLVM]] * [[http://eli.thegreenplace.net/2013/02/25/a-deeper-look-into-the-llvm-code-generator-part-1|A deeper look into the LLVM code generator, Part 1]] * [[http://llvm.org/docs/WritingAnLLVMBackend.html|Writing an LLVM Backend]] * 務必參閱 [[https://www.amazon.com/Getting-Started-LLVM-Core-Libraries/dp/1782166920|Getting Started with LLVM Core Libraries]] 第六章。 * LLVM CodeGen 會隨著時間演進。這裡只提供基本且概略的描述。 LLVM 後端基本上有三類 IR ([[http://llvm.org/devmtg/2011-09-16/EuroLLVM2011-LLVMplusARM.pdf|LLVM + ARM = ?]]),由上至下依序是: * SelectionDAG (DAG) * [[http://llvm.org/docs/CodeGenerator.html#target-independent-code-generation-algorithms|Target-independent code generation algorithms]] * MachineInstr (MI) * [[http://llvm.org/docs/CodeGenerator.html#machine-code-description-classes|Machine code description classes]] * MachineCode (MC) * [[http://llvm.org/docs/CodeGenerator.html#the-mc-layer|The “MC” Layer]] * 提供匯編器所需的抽象層,用來生成目的檔 (object file) 。 後兩者的名稱容易被混淆: - MachineInstr: SelectionDAG 最終會轉成 MachineInstr。MachineInstr 是目標機器碼的抽象,可以表示成 SSA 和 non-SSA 形式。在 register allocation 之前/後,分別為 SSA 和 non-SSA 形式。 - MachineCode: [[http://llvm.org/docs/CodeGenerator.html#code-emission|Code Emission]] 負責將 MachineInst 轉成 MachineCode。MachineCode 處理最後生成的機器碼 。 如同 [[http://reup.dmcs.pl/wiki/images/7/7a/Tricore-llvm-slides.pdf|Design and Implementation of a TriCore Backend for the LLVM Compiler Framework]] 第 12 頁所展示的,LLVM 後端的流程如下: LLVM IR -> DAG Lowering -> non-legalized DAGs -> DAG Legalization -> legalized DAGs -> Instruction Selection -> DAGs (native instructions, MI) -> Scheduling -> SSA Form -> SSA-based Opt -> SSA Form -> Register Allocation -> native instrictions with phys reg -> Post Allocation -> Prolog/Epilog Code Insertion -> resolved stack reference -> Peehole Opt -> Assembly Printing -> assembly [[http://llvm.org/devmtg/2012-04-12/Slides/Workshops/Anton_Korobeynikov.pdf|Tutorial: Building a backend in 24 hours (2012)]] 第 3 頁展示各個階段分別為何種形式的中介碼。 - LLVM IR -> DAG (平台無關): 這一步驟是為了將來方便做 instruction selection 和 instruction scheduling。LLVM 透過 tblgen 產生部分 instruction selector,後端需要客製化的部分另外寫成 C++ 代碼。請見 [[http://llvm.org/docs/CodeGenerator.html#introduction-to-selectiondags|Introduction to SelectionDAGs]]。在 instruction selection 之前包含底下幾個步驟: * 透過 [[http://llvm.org/doxygen/SelectionDAGBuilder_8h_source.html|SelectionDAGBuilder]] 將 LLVM IR 轉成 SelectionDAG,簡稱 DAG。所得到的 DAG 可能會出現目標平台不支援的型別或是運算,故稱此階段的 DAG 為 non-legalized DAG。 * [[http://llvm.org/docs/CodeGenerator.html#initial-selectiondag-construction|Initial SelectionDAG Construction]] * 將 non-legalized DAG 轉成 legalized DAG。先將目標平台不支援的型別消除,再消除目標平台不支援的運算。 * [[http://llvm.org/docs/CodeGenerator.html#selectiondag-legalizetypes-phase|SelectionDAG LegalizeTypes Phase]] * [[http://llvm.org/docs/CodeGenerator.html#selectiondag-legalize-phase|SelectionDAG Legalize Phase]] * 對 SelectionDAG 進行變換的過程中,需要 DAG Combiner 對變換後的 SelectionDAG 做優化和清理。 * [[http://llvm.org/docs/CodeGenerator.html#selectiondag-optimization-phase-the-dag-combiner|SelectionDAG Optimization Phase: the DAG Combiner]] - DAG (平台無關) -> DAG (平台相關): 做 instruction selection。透過 [[http://llvm.org/doxygen/classllvm_1_1SelectionDAGISel.html|SelectionDAGISel]] 遍歷 DAG 中的 SDNode,將平台無關指令替換成平台相關指令。注意! 此階段所得的目標代碼仍使用 virtual register,留待之後 register allocation 配置暫存器。 * [[http://llvm.org/docs/CodeGenerator.html#selectiondag-select-phase|SelectionDAG Select Phase]]。 - DAG (平台相關) -> DAG (平台相關): 做 instruction scheduling。透過 [[http://llvm.org/doxygen/ScheduleDAG_8h_source.html|ScheduleDAG]]對 DAG 進行 scheduling,這裡會根據目標平台的一些限制指定節點之間的順序。 * [[http://llvm.org/docs/CodeGenerator.html#selectiondag-scheduling-and-formation-phase|SelectionDAG Scheduling and Formation Phase]] - DAG (平台相關) -> MI: 透過 [[http://llvm.org/doxygen/InstrEmitter_8h_source.html|InstrEmitter]] 遍歷 DAG 生成 MI ([[http://llvm.org/doxygen/MachineInstr_8h_source.html|MachineInstr]])。此時的 MI 近似於 LLVM IR 的 SSA form (擁有 Phi Node),可以對其做 SSA 相關優化; MI 此時使用的仍是 virtual register。經過 register allocation 之後,MI 不再是 SSA form,同時也改使用 physical register。 - MI -> MC: 透過 [[http://llvm.org/doxygen/AsmPrinter_8h_source.html|AsmPrinter]] 遍歷 MI,轉換成 [[http://llvm.org/doxygen/MCInst_8h_source.html|MCInst]],最後輸出匯編或是目的檔。 * 延伸閱讀: * Register Allocation * [[http://llvm.org/devmtg/2008-08/Cheng_RegisterAllocation.pdf|LLVM Register Allocation (2008)]] * [[http://llvm.org/devmtg/2009-10/RegisterAllocationFutureWorks.pdf|Future Works in LLVM Register Allocation (2009)]] * [[http://llvm.org/devmtg/2011-11/#talk6|Register Allocation in LLVM 3.0 (2011)]] * [[https://www.slideshare.net/chimerawang/llvm-register-allocation-2nd-version|LLVM Register Allocation (2nd Version)]] * Phi-Elimination -> Register Coalescing -> Register Allocation -> Virtual Register Rewriter。 $ llc -debug-pass=Structure sum.ll Eliminate PHI nodes for register allocation Simple Register Coalescing Greedy Register Allocator Virtual Register Rewriter * [[http://llvm.org/doxygen/PHIElimination_8cpp_source.html|PHIElimination.cpp]]: ''PHIElimination::LowerPHINode()'' 改用 COPY 指令取代 PHI 指令。 * [[http://llvm.org/doxygen/RegisterCoalescer_8cpp_source.html|RegisterCoalescer.cpp]]: ''RegisterCoalescer::joinCopy()'' 如果 COPY 指令的來源和目的暫存器其 interval 相同,消除該 COPY 指令。 * [[http://llvm.org/doxygen/VirtRegMap_8cpp_source.html|VirtRegMap.cpp]]: 暫存器分配的結果存在 ''VirtRegMap'',紀錄虛擬暫存器和物理暫存器的對應,''VirtRegRewriter::runOnMachineFunction()'' 再根據 ''VirtRegMap'' 將 MI 中的虛擬暫存器改寫成物理暫存器。 * 底下範例可以看到 PHI 消除前後,COPY 被置換成目標平台指令和虛擬暫存器轉為物理暫存器的過程。 $ cat phi.c void func(bool r, bool y) { bool l = y || r; } $ clang++ -O0 phi.c -mllvm -print-machineinstrs -c # After Instruction Selection: # Machine code for function func: IsSSA, TracksLiveness BB#2: derived from LLVM BB %lor.end Predecessors according to CFG: BB#0 BB#1 %vreg1 = PHI %vreg6, , %vreg13, ; GR8:%vreg1,%vreg6,%vreg13 %vreg15 = AND8ri %vreg1, 1, %EFLAGS; GR8:%vreg15,%vreg1 MOV8mr , 1, %noreg, 0, %noreg, %vreg15; mem:ST1[%l] GR8:%vreg15 RETQ # After Eliminate PHI nodes for register allocation: # Machine code for function func: NoPHIs, TracksLiveness BB#2: derived from LLVM BB %lor.end Predecessors according to CFG: BB#0 BB#1 %vreg1 = COPY %vreg16; GR8:%vreg1,%vreg16 %vreg15 = AND8ri %vreg1, 1, %EFLAGS; GR8:%vreg15,%vreg1 MOV8mr , 1, %noreg, 0, %noreg, %vreg15; mem:ST1[%l] GR8:%vreg15 RETQ # After Fast Register Allocator: # Machine code for function func: NoPHIs, TracksLiveness, NoVRegs BB#2: derived from LLVM BB %lor.end Predecessors according to CFG: BB#0 BB#1 %AL = MOV8rm , 1, %noreg, 0, %noreg; mem:LD1[FixedStack3] %AL = AND8ri %AL, 1, %EFLAGS MOV8mr , 1, %noreg, 0, %noreg, %AL; mem:ST1[%l] RETQ * MC * [[http://llvm.org/devmtg/2010-11/Dunbar-MC.pdf|The LLVM Assembler & Machine Code Infrastructure]] * [[http://llvm.org/devmtg/2011-11/Grosbach_Anderson_LLVMMC.pdf|LLVM MC In Practice]] ===== SelectionDAG ===== * [[http://llvm.org/docs/GlobalISel.html|GlobalISel]] 預期將會取代 SelectionDAG。 * LLVM IR -> Global MI (GMI) -> MI * [[https://www.youtube.com/watch?v=6tfb344A7w8|2016 LLVM Developers’ Meeting: Global Instruction Selection Status]] * [[http://llvm.org/docs/CodeGenerator.html#target-independent-code-generation-algorithms|Target-independent code generation algorithms]] * [[http://llvm.org/devmtg/2007-05/04-Cheng-Codegen.pdf|LLVM Code Generator]] * [[http://llvm.org/devmtg/2008-08/Gohman_CodeGenAndSelectionDAGs.pdf|CodeGen Overview and Focus on SelectionDAGs]] * [[http://llvm.org/devmtg/2009-10/Korobeynikov_BackendTutorial.pdf|Tutorial: Building a backend in 24 hours]] * [[http://llvm.org/docs/WritingAnLLVMBackend.html#instruction-selector|Instruction Selector]] * 下載最新版 [[http://www.graphviz.org/Download_macos.php|Graphviz]],按照指示安裝 Graphviz 和相關軟體。(目前失效) * [[http://stackoverflow.com/questions/43372723/how-to-open-dot-on-mac|How to open dot on Mac]] * [[http://lists.llvm.org/pipermail/llvm-dev/2017-October/118393.html|[llvm-dev] LLVM ERROR: Cannot select: t29: v32f64 = X86ISD::VBROADCAST t9]] * 術語: * [[http://llvm.org/doxygen/ValueTypes_8h_source.html|EVT (Extended Value Type)]]: 包含 MVT 的超集,可以表示目標平台不支持的型別。 * [[http://llvm.org/doxygen/MachineValueType_8h_source.html|MVT (Machine Value Type)]]: 任一 LLVM 目標平台支持的型別皆屬於此類。 * [[http://llvm.org/doxygen/ISDOpcodes_8h_source.html|ISD]]: 可以指目標平台無關或相關的 SDNode。 * [[http://llvm.org/doxygen/classllvm_1_1SelectionDAG.html|DAG (SelectionDAG)]] * [[http://llvm.org/doxygen/classllvm_1_1SDValue.html|Chain/Op (SDValue)]]: SDNode 可能會產生多個 SDValue,''SDValue.getNode()'' 和 ''SDValue.getResNo()'' 分別代表該 SDValue 是從哪個 SDNode 生成,以及是該 SDNode 第幾個輸出。 * [[http://llvm.org/doxygen/classllvm_1_1TargetLowering.html|TLI (TargetLowering)]]: 後端負責 lowering SDNode。 * 重要檔案與常用接口: * [[http://llvm.org/doxygen/SelectionDAG_8h_source.html|SelectionDAG.h]]: SelectionDAG 類。 * [[http://llvm.org/doxygen/ValueTypes_8h_source.html|ValueTypes.h]]: SDNode result 型別。 * [[http://llvm.org/doxygen/ISDOpcodes_8h_source.html|ISDOpcodes.h]]: 目標平台無關的 Instruction Selection Dag 節點。目標平台可以在 TargetLowering 定義自己的 ISD 節點,如: [[http://llvm.org/doxygen/X86ISelLowering_8h_source.html|X86ISelLowering.h]]。 * [[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/TargetSelectionDAG.td|TargetSelectionDAG.td]]: SDNode 和 *.td 檔中 pattern 的對映。 * [[http://llvm.org/doxygen/classllvm_1_1SelectionDAG.html#abad5b17501ce8972fb04a149611e7177|SelectionDAG::getNode()]]: 生成目標平台無關或相關的 ISD 節點。常見於目標平台的 ISelLowering,如: [[http://llvm.org/doxygen/X86ISelLowering_8cpp_source.html|X86ISelLowering.cpp]]。 * [[http://llvm.org/doxygen/classllvm_1_1SelectionDAG.html#a1c04c72abd24de2572a03ef686a36dd6|SelectionDAG::getMachineNode()]] 建立 [[http://llvm.org/doxygen/classllvm_1_1MachineSDNode.html|MachineSDNode]] 節點,透過 [[http://llvm.org/doxygen/SelectionDAGISel_8h_source.html|SelectionDAG::ReplaceNode]] 替換目標平台無關的節點。常見於目標平台的 ISelDAGToDAG,如: [[http://llvm.org/doxygen/X86ISelDAGToDAG_8cpp_source.html|X86ISelDAGToDAG.cpp]]。 * 注意填入 operand 的順序,chain 也不能遺漏。後者在 O2 會爆發出難以查找的錯誤。[[http://llvm.org/doxygen/ISDOpcodes_8h_source.html|ISD]] 中的 INTRINSIC_WO_CHAIN,INTRINSIC_W_CHAIN 和 INTRINSIC_VOID 的註解說明第幾個 operand 代表 chain,第幾個 operand 代表真正的 operand。 * [[http://llvm.org/docs/CodeGenerator.html#introduction-to-selectiondags|Introduction to SelectionDAGs]] * 一個 DAG 基本對應一個 BasicBlock,DAG 中的每一個節點 (SDNode) 基本對應一條 LLVM IR。[[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/TargetSelectionDAG.td|TargetSelectionDAG.td]] 定義了 LLVM IR 對應的 SDNode。SDNode 之間是 producer-consumer 的關係,producer 產生 SDValue 給 consumer。SDValue 有底下三種型別: * concrete value type: 對映黑色實線。 * Other: 對映藍色虛線。 * Glue: 對映紅色實線。 {{http://eli.thegreenplace.net/images/2013/dag_imul5.png?400}} SDNode 之間有 data 或 control (chain, 簡寫 ch) dependency。上圖中黑色箭頭代表 data dependency,藍色箭頭代表 control dependency (注意它指向節點中的 ch 欄位),紅色箭頭代表兩個節點之間是 glue 的關係 (和 ch 相比,glue 之間不能插入其它節點)。EntryToken 和 TokenFactor 負責 control dependency。GraphRoot 代表 SelectionDAG 的最底層。每個節點中間一欄代表其 operator ([[http://llvm.org/doxygen/namespacellvm_1_1ISD.html#a22ea9cec080dd5f4f47ba234c2f59110|ISD::NodeType]]),上面一欄代表其輸入,下面一欄代表其輸出。 SelectionDAG 主要有底下幾個流程 ([[http://llvm.org/doxygen/SelectionDAGISel_8cpp_source.html|SelectionDAGISel::CodeGenAndEmitDAG()]]): - Lower: 將 LLVM IR 轉換成 SelectionDAG (SelectionDAGISel::SelectAllBasicBlocks)。 # Pop up a window to show dags before the first dag combine pass $ llc -view-dag-combine1-dags sum.ll - Combine: 將若干 SDNode 合成一個 SDNode ([[http://llvm.org/doxygen/classllvm_1_1SelectionDAG.html#ab3154fceed4e2389901aee563beae4e8|SelectionDAG::Combine()]])。 # Pop up a window to show dags before legalize types $ llc -view-legalize-types-dags sum.ll - Legalize Type: 將目標平台不支援的型別轉換成支援的型別 ([[http://llvm.org/doxygen/classllvm_1_1SelectionDAG.html#affe59e12b70bcd9f5476f7ab289fa84a|SelectionDAG::LegalizeTypes()]])。 # Pop up a window to show dags before the post legalize types dag combine pass $ llc -view-dag-combine-lt-dags sum.ll - Legalize Vector: 將目標平台不支援的向量型別轉換成支援的型別。 # Pop up a window to show dags before legalize $ llc -view-legalize-dags sum.ll - Legalize Op: 將目標平台不支援的運算轉換成支援的運算。 # Pop up a window to show dags before the second dag combine pass $ llc -view-dag-combine2-dags sum.ll - Combine: 將若干 SDNode 合成一個 SDNode。 # Pop up a window to show isel dags as they are selected $ llc -view-isel-dags sum.ll - Select: SDNode (平台無關) -> SDNode (平台相關)。 # Pop up a window to show sched dags as they are processed $ llc -view-sched-dags sum.ll - Schedule: 完成指令調度。 # Pop up a window to show SUnit dags after they are processed $ llc -view-sunit-dags sum.ll * [[http://llvm.org/doxygen/X86ISelLowering_8cpp_source.html|X86ISelLowering.cpp]] * 繼承 [[http://llvm.org/doxygen/classllvm_1_1TargetLowering.html|TargetLowering]],負責從 LLVM IR 轉成平台可以支持的 SDNode。 * [[http://llvm.org/doxygen/classllvm_1_1TargetLowering.html|TargetLowering]] 繼承 [[http://llvm.org/doxygen/classllvm_1_1TargetLoweringBase.html|TargetLoweringBase]]。TargetLoweringBase 提供 setAction 函式,指示針對特定型別的特定操作,後端如何處理。例如: [[http://llvm.org/doxygen/classllvm_1_1TargetLoweringBase.html#a07c0c67913bb543fc45ce9ef65ef260a|setOperationAction]] 設為 Custom 的需要在 [[http://llvm.org/doxygen/classllvm_1_1X86TargetLowering.html#aef185cdc1d7af43f6a97bf868da7d422|LowerOperation]] 處理。 * [[http://llvm.org/doxygen/classllvm_1_1TargetLowering.html#a48b383731144a31a360c35ab2860b696|LowerFormalArguments]] * [[http://llvm.org/doxygen/classllvm_1_1TargetLowering.html#ad927e7d64ce33cd1bba91fefe583bc85|LowerReturn]] * [[http://llvm.org/doxygen/classllvm_1_1TargetLoweringBase.html#a07debef9c174231d513e1966ef50cd0a|setTargetDAGCombine]]: 在 TargetLowering 註冊的 ISD,會經過 [[http://llvm.org/doxygen/classllvm_1_1TargetLowering.html#a23088f6e5065a437e6448022592ad85c|PerformDAGCombine]] 調用對應的函式,執行平台自定義的 Combine。 * [[http://lists.llvm.org/pipermail/llvm-dev/2015-July/088852.html|[LLVMdev] PerformDAGCombine vs. DAG to DAG]] * 如果 DAG 變換可以暴露其它 DAG 變換 (優化),則將該變換寫在 DAGCombine,否則寫在 DAG to DAG。 * [[http://lists.llvm.org/pipermail/llvm-dev/2016-October/106019.html|[llvm-dev] Target DAG-combine vs. Target custom lowering]] * Lowering 算是 legalization,只做一次; DAGCombine 算是優化,可做多次,在 legalization 之前或之後做。 * O2 情況下,後端需要處理 VECTOR 相關的 SDNode。例如: [[http://llvm.org/doxygen/X86ISelLowering_8cpp_source.html|LowerBUILD_VECTOR]][(http://lists.llvm.org/pipermail/llvm-dev/2017-May/112745.html)] * 某些情況下,針對目標平台不支持的 SDNode,可以在 [[http://llvm.org/doxygen/classllvm_1_1X86TargetLowering.html#a8330b55827791d73b9b061e660981b0c|PerformDAGCombine]] 將其轉換成目標平台支援的 SDNode。 * [[http://llvm.org/doxygen/X86ISelDAGToDAG_8cpp_source.html|X86ISelDAGToDAG.cpp]] * 負責將平台無關的 SDNode 換成平台相關的 SDNode。一般較為簡單的替換會寫成 *.td 檔 ([[http://llvm.org/docs/CodeGenerator.html#selectiondag-select-phase|SelectionDAG Select Phase]],善用 [[https://llvm.org/svn/llvm-project/llvm/trunk/include/llvm/Target/TargetSelectionDAG.td|TargetSelectionDAG.td]] 定義的 SDNode 寫匹配規則),複雜的替換在此檔寫成 C++ 函式。 * [[http://llvm.org/doxygen/X86ISelDAGToDAG_8cpp_source.html|X86DAGToDAGISel::Select()]] 是入口點。 * 預設經由 ''SelectCode(Node)'' 透過 *.td 檔所寫的匹配規則選擇指令,或者也可以針對特定 SDNode 生成其它 SDNode 進行置換。函式 ''SelectCode(Node)'' 由 tblgen 產生。 * [[http://llvm.org/doxygen/classllvm_1_1TargetFrameLowering.html#a8404705eb7a27e437ac51ca3730bfd7c|TargetFrameLowering::hasReservedCallFrame]] * 如果被調用的函式不需要 frame pointer (即 ![[http://llvm.org/doxygen/classllvm_1_1TargetFrameLowering.html#a524e1c2b3df4633c057ba8a4f6e2a3c4|TargetFrameLowering::hasFP]]),其參數所需要的棧空間會被配置在當前函式的棧上。 * [[https://www.quora.com/What-is-the-difference-between-a-stack-pointer-and-a-frame-pointer|What is the difference between a stack pointer and a frame pointer?]] * stack pointer 指向棧頂,該處為下一個可以使用的棧空間。當資料寫入 stack pointer 所指位址時,stack pointer 移動到下一個可以使用的位址。frame pointer 一般指向當前調用棧的底部 (或是某一固定點),因為 frame pointer 不會移動,所以可以作為參考點,存取當前棧上的變量。 * [[http://llvm.org/doxygen/classllvm_1_1TargetInstrInfo.html#a1f16d509bf2e2c2d0e0176f04c253a96|loadRegFromStackSlot]] /[[http://llvm.org/doxygen/classllvm_1_1TargetInstrInfo.html#a94d273699f7fdbd1a01477c4ca8014dd|storeRegToStackSlot]] * O2 暫存器分配會透過上述兩個函式做 spill。此處需注意,必須生成正確的指令,否則 O2 會有 runtime failure。 * [[https://stackoverflow.com/questions/31426486/how-to-make-llvm-prefer-one-machine-instruction-over-another|How to make LLVM prefer one machine instruction over another?]] * [[http://lists.llvm.org/pipermail/llvm-dev/2013-February/059236.html|[LLVMdev] pattern matching order]] * 使用 ''AddedComplexity'' 指定指令選擇的偏好。''AddedComplexity'' 可以為正或負數,數值越大,代表我們越傾向使用該指令。 * 能匹配到越多 SDNode (即越 complex) 的指令,其被選擇的優先級越高。 ===== Global Instruction Selection ===== * [[http://llvm.org/docs/GlobalISel.html|GlobalISel]] * [[https://www.youtube.com/watch?v=McByO0QgqCY|GlobalISel: Past, Present, and Future (2017)]] * [[https://www.youtube.com/watch?v=6tfb344A7w8|Global Instruction Selection Status (2016)]] * [[https://www.youtube.com/watch?v=F6GGbYtae3g|A Proposal for Global Instruction Selection (2015)]] ([[http://llvm.org/devmtg/2015-10/slides/Colombet-GlobalInstructionSelection.pdf|Slide]]) LLVM IR -> Global MI (GMI) -> MI * LLVM IR -> Global MI (GMI): [[http://llvm.org/doxygen/IRTranslator_8cpp_source.html|IRTranslator]] 負責將 LLVM IR 轉成 GMI,一條 LLVM IR 對映到零到多條 GMI。 * [[http://llvm.org/docs/GlobalISel.html#generic-machine-ir|Generic Machine IR]] * GMI -> MI: GMI 會經過 legalize 和 register bank select,再做 instruction selection 得到 MI。 ===== Instruction Scheduling ===== * [[https://www.youtube.com/watch?v=brpomKUynEA|Writing Great Machine Schedulers (2017)]] * 介紹 LLVM 以及 TableGen 如何客製化指令調度。 * [[https://www.youtube.com/watch?v=J0AtVzvPp_Y&feature=youtu.be|Scheduler for in-order processors (2016)]]([[http://llvm.org/devmtg/2016-09/slides/Absar-SchedulingInOrder.pdf|Slide]]) * 主要介紹 TableGen 指令調度相關的部分。 * [[http://llvm.org/devmtg/2014-10/Slides/Estes-MISchedulerTutorial.pdf|SchedMachineModel: Adding and Optimizing a Subtarget (2014)]] * 系統性的介紹 LLVM 以及 TableGen 指令調度相關的部分。 * [[http://llvm.org/devmtg/2012-11/Larin-Trick-Scheduling.pdf|Instruction scheduling for Superscalar and VLIW platforms Temporal perspective (2012)]] * 早期介紹 MI scheduler 和 TableGen 關於指令調度的部分。。 * [[https://engineering.purdue.edu/~milind/ece468/2015fall/lecture-08.pdf|Instruction scheduling]] * [[http://llvm.org/docs/CodeGenerator.html#selectiondag-scheduling-and-formation-phase|SelectionDAG Scheduling and Formation Phase]] * 使用 [[http://www.cs.cmu.edu/afs/cs/academic/class/15745-f03/public/lectures/L16_handouts.pdf|list scheduling]] ([[wp>Instruction scheduling]])。list scheduling 作用於一個 basic block。首先為該 basic block 建立一個 data dependency graph,根據 hardware resource 和 data dependence 為每個節點計算出 priority。從 cycle 0 開始,從 data dependency graph 挑出 priority 最高的節點,放進空閒的 function unit 執行。更新 data dependency graph,重新計算 priority,重複前一個步驟。 * 前述方法為 top-down list scheduling。如果把 data dependency graph 上面的 edge 反向,則為 bottom-up list scheduling。top-down list scheduling 是看節點的 depth 是否小於當前 cycle; bottom-up list scheduling 則是看節點的 high 是否小於當前 cycle,來決定是否要挑選該節點。 * 在 LLVM 中,instruction scheduler 共有三類。 * 兩類是 pre-register allocation scheduler,一類是 post-register allocation scheduler。 * 兩類中的第一個作用在 SelectionDAG,後兩者作用在 MI。 * 目前作用在 SelectionDAG 的 pre-register allocation scheduler 基本只負責將 SelectionDAG 轉成 MI。真正做調度的是作用在 MI 的 scheduler。 # 顯示 SDNode Sched DAG。 $ llc -view-sunit-dags sum.ll # 顯示 MI Sched DAG, pre-RA 和 post-RA。 $ llc -view-misched-dags sum.ll # pre-RA, SDNode $ llc -pre-RA-sched # pre-RA, MI $ llc -enable-misched # post-RA, MI $ llc -enable-post-misched * 術語: * [[http://llvm.org/doxygen/classllvm_1_1SUnit.html|SUnit (Scheduling Unit)]] * 提供一層抽象,既可表示 SDNode,也可以表示 MachineInstr,以便能在 SelectionDAG 和 MI 做指令調度。 * [[http://llvm.org/doxygen/classllvm_1_1SDep.html|SDep (Scheduling dependency)]] * 調度圖中,SUnit 是節點 (指令),SDep 是節點之間的有向邊 (表示依賴關係)。SUnit 的 depth 代表從該節點向上,直到沒有 predecessor 的節點為止,最長的路徑; SUnit 的 high 代表從該節點向下,直到沒有 successor 的節點為止,最長的路徑。 * [[http://llvm.org/doxygen/MachineScheduler_8cpp_source.html|SchedRegion]] * basic block 中,不考慮被調度的指令被視為 scheduling boundary,將 basic block 切分成數個 region。調度時,針對 region 依序處理。 * [[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/TargetItinerary.td|Itinerary]] * 原意為旅程,描述指令執行會經過的 pipeline stage。 * Bypass: 即 [[wp>Operand forwarding|data forwarding]] ([[https://stackoverflow.com/questions/40054344/pipelining-with-bypassing|Pipelining with bypassing]])。複習 [[wp>Classic RISC pipeline]]。Interlock 是處理 data hazard 的另一種方式,即在指令之間插入 nop。 * TD 檔案: * [[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/TargetSchedule.td|TargetSchedule.td]] 用 SchedMachineModel,SchedReadWrite 和 Itineraries 來描述調度訊息。 * SchedMachineModel 定義最基本的調度屬性。 class SchedMachineModel { int IssueWidth = -1; int MicroOpBufferSize = -1; int LoopMicroOpBufferSize = -1; int LoadLatency = -1; int HighLatency = -1; int MispredictPenalty = -1; ProcessorItineraries Itineraries = NoItineraries; bit PostRAScheduler = 0; bit CompleteModel = 1; list UnsupportedFeatures = []; bit NoModel = 0; } * 一般 subtarget 都會定義自己的 SchedMachineModel。[[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Hexagon/HexagonScheduleV60.td|HexagonScheduleV60.td]] def HexagonModelV60 : SchedMachineModel { // Max issue per cycle == bundle width. let IssueWidth = 4; let Itineraries = HexagonItinerariesV60; let LoadLatency = 1; let CompleteModel = 0; } * SchedReadWrite 可以針對指令的輸入輸出做更精細的設定。 * [[https://youtu.be/brpomKUynEA?t=14m56s|Writing Great Machine Schedulers (2017)]] class SchedReadWrite; class Sched schedrw> { list SchedRW = schedrw; } class SchedWrite : SchedReadWrite; class SchedRead : SchedReadWrite; class ProcResource : ProcResourceKind, ProcResourceUnits; class WriteRes resources> : ProcWriteResources { SchedWrite WriteType = write; } class ReadAdvance writes = []> : ProcReadAdvance { SchedRead ReadType = read; } * 針對目標平台的 FuncUnit,定義 SchedRead/SchedWrite 和 ProcResource。再將指令操作數和對應的 SchedRead/SchedWrite 綁定。針對 SchedRead 設定 ReadAdvance; 針對 SchedWrite 設定 WriteRes。[[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/ARM/ARMSchedule.td|ARMSchedule.td]] 和 [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/ARM/ARMScheduleR52.td|ARMScheduleR52.td]] // Basic ALU operation. def WriteALU : SchedWrite; def ReadALU : SchedRead; def R52UnitALU : ProcResource<2> { let BufferSize = 0; } // Int ALU def : WriteRes { let Latency = 3; } def : ReadAdvance; * 針對 subtarget 微調。SchedWriteRes SchedReadAdvance 搭配 InstRW 和 instregex 使用。 * [[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/TargetItinerary.td|TargetItinerary.td]] 定義指令調度所需要的資訊。FuncUnit 描述功能單元。Bypass 描述可以將運算結果 forward 的通路。InstrItinData 針對屬於同一個 InstrItinClass 的指令,描述其 InstrStage (InstrStage 描述指令完成該 stage 所需的 FuncUnit 和 cycle 數),其運算元於第幾個 cycle 準備好,以及 Bypass 訊息。 class ProcessorItineraries fu, list bp, list iid> { list FU = fu; list BP = bp; list IID = iid; } * [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Hexagon/HexagonSchedule.td|HexagonSchedule.td]] 定義基本的FuncUnit,Bypass 和 InstrItinClass。不同系列的晶片可以定義自己的指令調度資訊,例如: [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Hexagon/HexagonScheduleV4.td|HexagonScheduleV4.td]] 定義 ProcessorItineraries 和 SchedMachineModel,其中 SchedMachineModel 會使用到 ProcessorItineraries。 * 其它檔案: * [[http://llvm.org/doxygen/ScheduleDAG_8h.html|ScheduleDAG.h]] * [[http://llvm.org/doxygen/classllvm_1_1ScheduleDAGSDNodes.html|ScheduleDAGSDNodes]] 作用於 SDNode,為 pre-register allocation DAG scheduler 提供接口,''lib/CodeGen/SelectionDAG/'' 底下有幾個實現。[[http://llvm.org/doxygen/classllvm_1_1ScheduleDAGInstrs.html|ScheduleDAGInstrs]] 作用於 MI,為 pre-register allocation/post-register MI scheduler 提供接口。''lib/Target/'' 底下各目標平台有各自的實現,均繼承 [[http://llvm.org/doxygen/classllvm_1_1ScheduleDAGMILive.html|ScheduleDAGMILive]] (ScheduleDAGMILive 考慮暫存器壓力。[(http://lists.llvm.org/pipermail/llvm-dev/2017-April/112287.html)]) * {{http://llvm.org/doxygen/classllvm_1_1ScheduleDAG__inherit__graph.png?900}} * [[http://llvm.org/doxygen/classllvm_1_1DefaultVLIWScheduler.html|DefaultVLIWScheduler]] 只用在 [[http://llvm.org/doxygen/classllvm_1_1VLIWPacketizerList.html#acfb2315913d694fb3f1144279ab75a85|VLIWPacketizerList::PacketizeMIs]]。DefaultVLIWScheduler 僅是 [[http://llvm.org/doxygen/classllvm_1_1ScheduleDAGInstrs.html|ScheduleDAGInstrs]] 此一抽象類的實例化。 * [[http://llvm.org/doxygen/MachineScheduler_8h_source.html|MachineScheduler.h]] 是 MI scheduler 重要檔案。 * [[http://llvm.org/doxygen/classllvm_1_1ScheduleDAGMILive.html|ScheduleDAGMILive]] * 基本上 MI scheduler 都繼承於它。入口點是 ''schedule'',在 MachineScheduler/PostMachineScheduler/PostRASchedulerList 的 ''runOnMachineFunction'' 被調用。 * [[http://llvm.org/doxygen/ScheduleDAGMutation_8h.html|ScheduleDAGMutation]] * 可以調整 DAG 節點之間的依賴關係。於 ''ScheduleDAGMI::postprocessDAG'' 在 ''ScheduleDAGMILive::schedule'' 被調用。 * 參考 [[http://llvm.org/doxygen/structllvm_1_1HexagonSubtarget_1_1HVXMemLatencyMutation.html|HexagonSubtarget::HVXMemLatencyMutation]]。注意,調整 latency,必須同時調整 predecessor 和 successor 的 latency。改變 successor 的 latency,必須將 predecessor 的 height 設成 dirty; 反之,改變 predecessor 的 latency,必須將 successor 的 depth 設成 dirty。 * [[http://llvm.org/doxygen/classllvm_1_1MachineSchedStrategy.html|MachineSchedStrategy]] * MachineSchedStrategy 可以作為參數傳遞給 ScheduleDAGMILive。其客製化的 ''pickNode'' 和 ''schedNode'' 在 ''ScheduleDAGMILive::schedule'' 被調用。。 * [[http://llvm.org/doxygen/classllvm_1_1GenericScheduler.html|GenericScheduler]] 為 MachineSchedStrategy 預設實現。{{http://llvm.org/doxygen/classllvm_1_1MachineSchedStrategy__inherit__graph.png?900}} * [[http://llvm.org/doxygen/MachineScheduler_8cpp_source.html|MachineScheduler.cpp]] 和 [[http://llvm.org/doxygen/PostRASchedulerList_8cpp_source.html|PostRASchedulerList]] 是 MI scheduler pass 重要檔案。MI scheduler pass 在 ''runOnMachineFunction'' 中,會調用到 MI scheduler 的 ''schedule'' 進行調度。 * MachineScheduler 是 pre-register allocation scheduler pass。 * PostMachineScheduler 是 post-register allocation scheduler pass。 * PostRASchedulerList 是 post-register allocation scheduler pass。預設執行這一個,而不是前面的 PostMachineScheduler。 * ScheduleDAGMI 和 ScheduleDAGMILive 主要差異請看各自的 ''schedule()'' 實現 ([[http://llvm.org/doxygen/classllvm_1_1ScheduleDAGMI.html#a5d7e71cf32573e6b1762b5a1d82a1cf5|ScheduleDAGMI::schedule()]][[http://llvm.org/doxygen/classllvm_1_1ScheduleDAGMILive.html#a1583ce23a69e8a1b4af8065e2019c75f|ScheduleDAGMILive::schedule()]])。 * ScheduleDAGMILive 基本上是在 ScheduleDAGMI 的基礎上,增加暫存器壓力的紀錄。 buildSchedGraph(AA); findRootsAndBiasEdges(TopRoots, BotRoots); SchedImpl->initialize(this); // SchedImpl 為 MachineSchedStrategy 的實例。 initQueues(TopRoots, BotRoots); bool IsTopNode = false; while (true) { // 如果 SU 為 nullptr,或為非法,跳出循環。 SUnit *SU = SchedImpl->pickNode(IsTopNode); // ScheduleDAGMILive 調用 scheduleMI,其基礎為原來 ScheduleDAGMI 的代碼。 // 此處透過 moveInstruction 調整 SU 對應 MI 在原來 basic block 的位置。 SchedImpl->schedNode(SU, IsTopNode); updateQueues(SU, IsTopNode); } * [[http://llvm.org/doxygen/classllvm_1_1ScheduleDAGInstrs.html#a43d828dab9502a17822c16d108cb0b9a|ScheduleDAGInstrs::buildSchedGraph()]]: 為 basic block 中 region 的 MachineInstr 建立 SUnit,並藉由 MachineInstr 引用的暫存器建立指令之間的依賴關係 (記錄在[[http://llvm.org/doxygen/classllvm_1_1SUnit.html|SUnit]] 中的 Preds 和 Succs)。 [[http://llvm.org/doxygen/classllvm_1_1ScheduleDAGMILive.html#a4be5ffcd4f76d433cc753be146a872b7|ScheduleDAGMILive::buildDAGWithRegPressure()]] 在前者的基礎上增加暫存器壓力的計算。 * [[http://llvm.org/doxygen/classllvm_1_1ScheduleDAGMI.html#a6d0a0b4903e8d4d12c98b0f43fe83878|ScheduleDAGMI::findRootsAndBiasEdges()]]: Data dependency graph 的 top 和 bottom root 節點因為沒有依賴其它節點,可以排入 schedule。 * [[http://llvm.org/doxygen/classllvm_1_1ScheduleDAGMI.html#a1f98694f104d052d71ed74ade38d69f0|ScheduleDAGMI::initQueues()]]: 將 top 和 bottom root 節點排入 SchedBoundary 所維護的 [[http://llvm.org/doxygen/classllvm_1_1ReadyQueue.html|ReadyQueue]]。 * [[http://llvm.org/doxygen/classllvm_1_1ScheduleDAGMI.html#abfdd9a95217810c69b2557060a130318|ScheduleDAGMI::updateQueues()]]: 前一條指令被調度過後,重新計算各條指令的優先級,並更新 [[http://llvm.org/doxygen/classllvm_1_1ReadyQueue.html|ReadyQueue]]。 * 如果是 top-down list scheduling,調用[[http://llvm.org/doxygen/classllvm_1_1ScheduleDAGMI.html#acf56d667066b39177a3c0f134d759d98|ScheduleDAGMI::releaseSuccessors()]] 釋放已被調度指令的 successor,使其可以被調度。 * 可以看到主要透過 SchedImpl (實現 MachineSchedStrategy 的 GenericScheduler) 的 pickNode 和 schedNode 做指令調度。以 [[http://llvm.org/doxygen/classllvm_1_1GenericScheduler.html#ad4cc558b6cbcc4e9cea5df915c197e14|GenericScheduler::pickNode()]] 和 [[http://llvm.org/doxygen/classllvm_1_1GenericScheduler.html#adf574ab3455d7292bed998d8ba50bfeb|GenericScheduler::schedNode()]] 為例。 * GenericScheduler 裡,top-down 和 bottom-up 分別對應到 [[http://llvm.org/doxygen/classllvm_1_1SchedBoundary.html|SchedBoundary]] Top 和 Bot。每個 SchedBoundary 各自有 [[http://llvm.org/doxygen/classllvm_1_1ReadyQueue.html|ReadyQueue]] (Available 和 Pending。從 Available 取節點,因為 interlock 的節點會被放到 Pending,之後再轉移到 Available)。 * [[http://llvm.org/doxygen/classllvm_1_1SchedBoundary.html|SchedBoundary]]: 對於欲做指令調度的 MI DAG 而言,我們基本會維護 top-down 和 bottom-up 兩個方向的 SchedBoundary,記錄可以被調度的節點和其它相關訊息。 * [[http://llvm.org/doxygen/structllvm_1_1GenericSchedulerBase_1_1SchedCandidate.html|SchedCandidate]]: 對 SchedBoundary 的 [[http://llvm.org/doxygen/classllvm_1_1ReadyQueue.html|ReadyQueue]] 中的可供調度的節點,我們還要經過一些算法評估 ([[http://llvm.org/doxygen/classllvm_1_1GenericScheduler.html#a2394f6619e48d28d58883bc91a8ac919|GenericScheduler::tryCandidate()]]),從中挑選出合適的候選者 (SchedCandidate)。 * pickNode 以命令行參數為優先,調用 [[http://llvm.org/doxygen/classllvm_1_1GenericScheduler.html#ad9779ec3011b4547f9a509af82d87a6e|pickNodeFromQueue]] 以 top-down 或是 bottom-up 方式取得下一個欲調度的節點。如果沒有命令行參數,調用 [[http://llvm.org/doxygen/classllvm_1_1GenericScheduler.html#aef82bce77971408815c3b90979188d1e|pickNodeBidirectional]] 根據 IsTopNode 以 top-down 或是 bottom-up 方式取得下一個欲調度的節點。 * schedNode 根據 pickNode 所挑出的節點,更新擴展 top-down 或 bottom-up 的 SchedBoundary ([[http://llvm.org/doxygen/classllvm_1_1SchedBoundary.html#a6b1771cf492495f8f82727657c68e571|SchedBoundary::bumpNode]]),和其它相關資訊。 * Hexagon 使用到 VILW 相關的指令調度。 * [[http://llvm.org/docs/CodeGenerator.html#vliw-packetizer|VLIW Packetizer]] * 術語: * Packet: 即 [[wp>Explicitly parallel instruction computing|VLIW]] 中的 bundle。 * RPTracker: [[http://llvm.org/doxygen/classllvm_1_1RegPressureTracker.html|RegPressureTracker]]。用來紀錄當前 virtual register pressure,避免指令調度造成後續暫存器分配產生 spill。 * RPTracker will cover the entire DAG region, TopTracker and BottomTracker will be initialized to the top and bottom of the DAG region without covereing any unscheduled instruction. * PreRA scheduler * [[http://llvm.org/doxygen/HexagonMachineScheduler_8h_source.html|HexagonMachineScheduler.h]] * [[http://llvm.org/doxygen/classllvm_1_1VLIWResourceModel.html|VLIWResourceModel]] * 包裝 DFAPacketizer (ResourcesModel) 和 TargetSchedModel (SchedModel)。主要用來看 SU 是否能放在當前的 Packet (''VLIWResourceModel::isResourceAvailable()'')。 * [[http://llvm.org/doxygen/classllvm_1_1VLIWMachineScheduler.html|VLIWMachineScheduler]] * VLIWMachineScheduler 繼承 ScheduleDAGMILive,ScheduleDAGMILive 做 instruction scheduling 的同時,也考慮 register pressure。因此可以知道 VLIWMachineScheduler 在 register allocation 之前執行。VLIWMachineScheduler 基本只是 override ScheduleDAGMILive 的 ''schedule()'' 接口,供上層回調。 * [[http://llvm.org/doxygen/classllvm_1_1ConvergingVLIWScheduler.html|ConvergingVLIWScheduler]] * ConvergingVLIWScheduler 繼承 MachineSchedStrategy,主要 override MachineSchedStrategy 的 pickNode 和 schedNode。 * [[http://llvm.org/doxygen/HexagonMachineScheduler_8cpp_source.html|HexagonMachineScheduler.cpp]] * 入口點位於 ''schedule()''。實際挑選節點,並更新 data dependency graph 各節點 priority 的工作交給 SchedImpl (即 ''ConvergingVLIWScheduler'')。 * ''HexagonPassConfig'' 複寫 ''createMachineScheduler'',回傳 VLIWMachineScheduler。''createMachineScheduler'' 在 ''MachineScheduler::runOnMachineFunction'' 被調用。透過這樣的方式,Hexagon 執行自己的 PreRA scheduler。 * PostRA scheduler * 採用 [[http://llvm.org/doxygen/PostRASchedulerList_8cpp_source.html|PostRASchedulerList]]。Hexagon 分別透過 ''getHazardType'' 和 ''EmitInstruction'' 改變調度結果。 ScheduleHazardRecognizer::HazardType HexagonHazardRecognizer::getHazardType(SUnit *SU, int stalls) { if (!Resources->canReserveResources(*MI)) { DEBUG(dbgs() << "*** Hazard in cycle " << PacketNum << ", " << *MI); HazardType RetVal = Hazard; if (TII->mayBeNewStore(*MI)) { // Make sure the register to be stored is defined by an instruction in the // packet. MachineOperand &MO = MI->getOperand(MI->getNumOperands() - 1); if (!MO.isReg() || RegDefs.count(MO.getReg()) == 0) return Hazard; // The .new store version uses different resources so check if it // causes a hazard. MachineFunction *MF = MI->getParent()->getParent(); MachineInstr *NewMI = MF->CreateMachineInstr(TII->get(TII->getDotNewOp(*MI)), MI->getDebugLoc()); if (Resources->canReserveResources(*NewMI)) RetVal = NoHazard; DEBUG(dbgs() << "*** Try .new version? " << (RetVal == NoHazard) << "\n"); MF->DeleteMachineInstr(NewMI); } return RetVal; } } * Packetizer * [[http://llvm.org/doxygen/DFAPacketizer_8h.html|DFAPacketizer.h]] ([[https://gcc.gnu.org/news/dfa.html|DFA Scheduler]]) * DFA Packetizer/Scheduler 用有限狀態機 (DFA) 向指令調度器描述目標平台的 [[https://en.wikipedia.org/wiki/Hazard_(computer_architecture)#Structural_hazards|Structural hazards]]。 * [[http://llvm.org/doxygen/classllvm_1_1VLIWPacketizerList.html|VLIWPacketizerList]] 可以被目標平台繼承。[[http://llvm.org/doxygen/HexagonVLIWPacketizer_8h_source.html|HexagonVLIWPacketizer.h]] 為 VLIWPacketizerList 其中一個實現。 * VLIWPacketizerList 包含底下成員: * [[http://llvm.org/doxygen/classllvm_1_1DFAPacketizer.html|DFAPacketizer]] 作為 ResourceTracker,用於判斷是否有足夠硬件資源將 MI 打包進當前的 Packet。 * CurrentPacketMIs 代表當前 Packet 中所包含的 MI。 * VLIWPacketizerList 包含底下函式: * [[http://llvm.org/doxygen/classllvm_1_1VLIWPacketizerList.html#acfb2315913d694fb3f1144279ab75a85|PacketizeMIs]] 是 Packetizer 的入口函式,於 ''HexagonPacketizer::runOnMachineFunction'' ([[http://llvm.org/doxygen/HexagonVLIWPacketizer_8cpp_source.html|HexagonVLIWPacketizer.cpp]]) 被調用。Machine Basic Block 會被切分成數個 Region,再由 [[http://llvm.org/doxygen/classllvm_1_1VLIWPacketizerList.html#acfb2315913d694fb3f1144279ab75a85|PacketizeMIs]] 將 Region 中的數個 MI 打包成一個 Packet (Bundle)。''PacketizeMIs'' 先對 Region 中的指令做調度,之後根據 DFAPacketizer 偵測 [[https://en.wikipedia.org/wiki/Hazard_(computer_architecture)#Structural_hazards|Structural hazards]],根據 [[http://llvm.org/doxygen/classllvm_1_1VLIWPacketizerList.html#a2ed4f250d154c460d2acd08c81aae5b3|isLegalToPacketizeTogether]]和 [[http://llvm.org/doxygen/classllvm_1_1VLIWPacketizerList.html#a9cec2dc6fdf9c6662c833a91db3b7db3|isLegalToPruneDependencies]] 偵測 [[https://en.wikipedia.org/wiki/Hazard_(computer_architecture)#Data_hazards|Data hazards]],決定 MI 是否能加入當前的 Packet。 * [[http://llvm.org/doxygen/classllvm_1_1VLIWPacketizerList.html#ae687f7292a0b3db97788f744d37d85d4|addToPacket]] 於 PacketizeMIs 中被調用,將 MI 加入當前的 Packet,CurrentPacketMIs。 * [[http://llvm.org/doxygen/classllvm_1_1VLIWPacketizerList.html#ac05188b9f403d9279681683e2a7b3d3e|endPacket]] 結束當前 Packet。''finalizeBundle'' ([[http://llvm.org/doxygen/MachineInstrBundle_8cpp_source.html|MachineInstrBundle.cpp]]) 透過 [[http://llvm.org/doxygen/classllvm_1_1MIBundleBuilder.html|MIBundleBuilder]] 將 CurrentPacketMIs 中的 MI 標記其屬於某一個 Packet。 * Hexagon 有 packetize 的測試用例。 $ lldb llc (lldb) b HexagonPacketizer::runOnMachineFunction (lldb) b VLIWPacketizerList::PacketizeMIs (lldb) r test/CodeGen/Hexagon/packetize_cond_inst.ll * [[http://llvm.org/doxygen/classllvm_1_1TargetPassConfig.html|TargetPassConfig]] 有兩個虛擬函式 ''createMachineScheduler'' 和 ''createPostMachineScheduler'' 可以被覆寫。可以返回 ScheduleDAGInstrs 的子類,或是只返回 ScheduleDAGInstrs 的實例,傳入客制的 MachineSchedStrategy。又或是透過 ''substitutePass'' 將預設執行的 PostRASchedulerList 替換成 PostMachineScheduler。 * 目前有 ARM/AArch64 和 SystemZ 使用 PostMachineScheduler。ARM/AArch64 使用預設的 MachineSchedStrategy,PostGenericScheduler,並加上自訂的 Mutation。SystemZ 使用自訂的 SystemZPostRASchedStrategy。 * {{http://llvm.org/doxygen/classllvm_1_1GenericSchedulerBase__inherit__graph.png?350}} * [[http://llvm.org/doxygen/classllvm_1_1PostGenericScheduler.html|PostGenericScheduler]] 和用於 pre-register allocation 的 [[http://llvm.org/doxygen/classllvm_1_1GenericScheduler.html|GenericScheduler]] 不同。PostGenericScheduler 只做 top-down scheduling。專注 PostGenericScheduler::pickNode。 * [[http://llvm.org/doxygen/structllvm_1_1GenericSchedulerBase_1_1SchedCandidate.html|SchedCandidate]]: 存放候選節點相關訊息。[[http://llvm.org/doxygen/classllvm_1_1PostGenericScheduler.html#ad9f7d64df62c7391f08ec630ea104e71|tryCandidate()]] 挑選節點。 * [[http://llvm.org/doxygen/structllvm_1_1GenericSchedulerBase_1_1CandPolicy.html|CandPolicy]]: 於此候選節點所在區域中,挑選下一個候選節點所採用的策略。可以傾向 resource 受限或是 latency 受限,前者反映 structural hazard,後者反映 data hazard。透過 [[http://llvm.org/doxygen/classllvm_1_1GenericSchedulerBase.html#a8668556014566994c07b21391762551b|setPolicy()]] 設置。 * [[http://llvm.org/doxygen/classllvm_1_1GenericSchedulerBase.html#ae0da1bc94e326020069c0f44170a48d3|CandReason]]: 表示該節點被選上的原因。原因之間有優先級之分。以 [[http://llvm.org/doxygen/MachineScheduler_8cpp.html#a9b112d0cb3309a5fda2d6b7601e89e3f|tryLess()]] 為例: // 取 TryVal 和 CandVal 中最小的。 static bool tryLess(int TryVal, int CandVal, GenericSchedulerBase::SchedCandidate &TryCand, GenericSchedulerBase::SchedCandidate &Cand, GenericSchedulerBase::CandReason Reason) { if (TryVal < CandVal) { TryCand.Reason = Reason; return true; } if (TryVal > CandVal) { // Reason 越小,優先級越高。把 Cand.Reason 替換掉。 if (Cand.Reason > Reason) Cand.Reason = Reason; return true; } return false; } * 如果 TryVal 和 CandVal 相等,返回 false,進行下一條件比較。 * 目前有底下幾個方式修改調度策略 ([[https://youtu.be/brpomKUynEA?t=32m43s|Writing Great Machine Schedulers (2017)]])。 * 實現 [[http://llvm.org/doxygen/classllvm_1_1TargetSubtargetInfo.html#a9cd7c54e0bb13cc01c4e2a4133a40ee4| overrideSchedPolicy()]]。 * 實現 [[http://llvm.org/doxygen/classllvm_1_1MachineSchedStrategy.html|MachineSchedStrategy]]。 * 實現 [[http://llvm.org/doxygen/classllvm_1_1ScheduleDAGMutation.html|ScheduleDAGMutation]]。 ==== Example ==== * 以 Hexagon 為例,觀察其使用的 scheduler。 $ llc -O2 -debug-pass=Structure test/CodeGen/Hexagon/packetize_cond_inst.ll * Machine Instruction Scheduler ([[http://llvm.org/doxygen/MachineScheduler_8cpp_source.html|MachineScheduler.cpp]]): pre-register allocation MI scheduler。 * Post RA top-down list latency scheduler ([[http://llvm.org/doxygen/PostRASchedulerList_8cpp_source.html|PostRASchedulerList.cpp]]): post-register allocation MI scheduler。 * 從 ''runOnMachine'' 開始,直到 ''SchedulePostRATDList::ListScheduleTopDown'' 開始實際的調度。 void SchedulePostRATDList::ListScheduleTopDown() { while (!AvailableQueue.empty() || !PendingQueue.empty()) { } * 主要維持兩個佇列,AvailableQueue 和 PendingQueue。如果 PendingQueue 中的指令,其結果已經運算完成,會被放到 AvailableQueue。最終會從 AvailableQueue 中挑出當前 cycle 要執行的指令。 * 以 Hexagon 為例,判讀 debug 訊息。 $ llc -O2 -debug-only=post-RA-sched test/CodeGen/Hexagon/packetize_cond_inst.ll ********** List Scheduling ********** SU(0): %P0 = C4_cmplte %R2, %R1 # preds left : 0 # succs left : 3 # rdefs left : 0 Latency : 1 Depth : 0 Height : 4 Successors: SU(2): Data Latency=1 Reg=%P0 SU(1): Data Latency=1 Reg=%P0 SU(1): Anti Latency=0 SU(1): %R1 = A2_paddt %P0, %R2, %R1 # preds left : 2 # succs left : 2 # rdefs left : 0 Latency : 1 Depth : 1 Height : 3 Predecessors: SU(0): Data Latency=1 Reg=%P0 SU(0): Anti Latency=0 Successors: SU(2): Out Latency=1 SU(2): Data Latency=1 Reg=%R1 * 一開始印出 SUnit 在 dependency graph 中的各種屬性,以及和其它 SUnit 的依賴關係。 * 接著打印出調度的過程。 Reset hazard recognizer *** Examining Available *** Scheduling [0]: SU(0): %P0 = C4_cmplte %R2, %R1 Add instruction %P0 = C4_cmplte %R2, %R1 *** Examining Available *** Finished cycle 0 Advance cycle, clear state * ''***'' 開頭的訊息,是由[[http://llvm.org/doxygen/PostRASchedulerList_8cpp_source.html|SchedulePostRATDList::ListScheduleTopDown()]] 生成。其它訊息由 HazardRecognizer 選擇性生成 (如: [[http://llvm.org/doxygen/HexagonHazardRecognizer_8cpp_source.html|HexagonHazardRecognizer.cpp]])。顯示在第幾個 cycle,選中哪一條指令。 ===== TableGen ===== * [[http://llvm.org/devmtg/2012-04-12/Slides/Reed_Kotler.pdf|Tablegen Deep Dive (2012)]] ([[http://llvm.org/devmtg/2012-04-12/videos/Reed_Kotler-desktop.mov|Video]] [[https://github.com/azru0512/tblgen|Example]]) * [[https://nhaehnle.blogspot.de/2018/02/tablegen-1-what-has-tablegen-ever-done.html|TableGen #1: What has TableGen ever done for us?]] * 可以參考 test/TableGen 底下的測試用例。例如: [[https://github.com/llvm-mirror/llvm/blob/master/test/TableGen/SetTheory.td|SetTheory.td]]。 * [[http://llvm.org/docs/TableGen/index.html|TableGen]]: TableGen 語法近似 C++ class template,借用函數式編程相關的語法。 * [[http://llvm.org/docs/TableGen/LangIntro.html|TableGen Language Introduction]]: 需要寫 *.td 可以參考這份文件。 * TableGen 裡所提到的 backend ([[https://llvm.org/docs/TableGen/BackEnds.html|TableGen BackEnds]]),指的是其命令行選項,如: ''-print-records'' (''-print-records'' 是預設後端)。 * TableGen 裡的 ''class'' 和 ''def'' 都是 record。 ''class'' 類似 C++ 裡的 Class,''def'' 類似 C++ 裡的 Object。 * [[http://llvm.org/docs/TableGen/LangRef.html|TableGen Language Reference]]: 需要 parsing *.td 可以參考這份文件。 * TableGen 支持的型別: * [[http://llvm.org/docs/TableGen/LangIntro.html#the-tablegen-type-system|The TableGen type system]] * [[http://llvm.org/docs/TableGen/LangRef.html#types|Types]]。 * TableGen 支持的值: * [[http://llvm.org/docs/TableGen/LangIntro.html#the-tablegen-type-system|TableGen values and expressions]] * [[http://llvm.org/docs/TableGen/LangRef.html#values|Values]] * dag: dag 的第一個元素是 operator,必須是 record definition。dag 其實是用 lisp 語法描述的 tree,而非 directed acyclic graph。 * [[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/Target.td|Target.td]] 描述一些 dag 的操作符,如: ''outs'' 和 ''ins'',分別用於 ''(outs R32:$dst)'' 和 ''(ins R32:$src1, R32:$src2)''。 * [[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/TargetSelectionDAG.td|TargetSelectionDAG.td]] 描述一些 dag 的操作符,如: ''set''。 * Register ([[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/Target.td|Target.td]]) * SubRegIndex * ''list SubRegs'' 和 ''list SubRegIndices'' 一起搭配。前者定義此 register 包含哪些 sub-register,後者用來定位 (index) 這些 sub-register。以 [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/X86/X86RegisterInfo.td|X86RegisterInfo.td]] 為例: class X86Reg Enc, list subregs = []> : Register { let Namespace = "X86"; let HWEncoding = Enc; let SubRegs = subregs; } let Namespace = "X86" in { def sub_8bit : SubRegIndex<8>; def sub_8bit_hi : SubRegIndex<8, 8>; // 此 subreg 大小為 8 bit,在 reg 從 offset 8 bit 開始。 def sub_16bit : SubRegIndex<16>; def sub_32bit : SubRegIndex<32>; def sub_xmm : SubRegIndex<128>; def sub_ymm : SubRegIndex<256>; } let SubRegIndices = [sub_8bit, sub_8bit_hi], CoveredBySubRegs = 1 in { def AX : X86Reg<"ax", 0, [AL,AH]>; // AX 由 AL 和 AH 兩個 subreg 組成,AL 和 AH 如何在 AX 佔位由 SubRegIndices 描述。 def DX : X86Reg<"dx", 2, [DL,DH]>; def CX : X86Reg<"cx", 1, [CL,CH]>; def BX : X86Reg<"bx", 3, [BL,BH]>; } * RegisterClass。以 [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/X86/X86RegisterInfo.td|X86RegisterInfo.td]] 為例: class RegisterClass regTypes, int alignment, dag regList, RegAltNameIndex idx = NoRegAltName> def GR32 : RegisterClass<"X86", [i32], 32, (add EAX, ECX, EDX, ESI, EDI, EBX, EBP, ESP, R8D, R9D, R10D, R11D, R14D, R15D, R12D, R13D)>; * Instruction ([[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/Target.td|Target.td]]) * 其中的 ''list Pattern'' 欄位即是用來撰寫指令選擇所需要的 pattern match。 * 目標平台撰寫的指令都是 Instruction 的子類。以 Sparc 為例 ([[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Sparc/SparcInstrInfo.td|SparcInstrInfo.td]]) def XNORrr : F3_1<2, 0b000111, (outs IntRegs:$rd), 輸出 (ins IntRegs:$rs1, IntRegs:$rs2), 輸入 "xnor $rs1, $rs2, $rd", // 匯編指令 [(set i32:$rd, (not (xor i32:$rs1, i32:$rs2)))] // DAG 匹配樣式,$rd = not ($rs1 xor $rs2) >; * ''F3_1'' 定義於 [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Sparc/SparcInstrFormats.td|SparcInstrFormats.td]],繼承自 Instruction ([[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/Target.td|Target.td]])。 * 匹配到 DAG ''$rd = not ($rs1 xor $rs2)'' 之後,會將其替換成 ''XNORrr''。 * Operand ([[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/Target.td|Target.td]]) * ValueType ([[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/CodeGen/ValueTypes.td|ValueTypes.td]]) class Operand : DAGOperand { ValueType Type = ty; string PrintMethod = "printOperand"; string EncoderMethod = ""; bit hasCompleteDecoder = 1; string OperandType = "OPERAND_UNKNOWN"; dag MIOperandInfo = (ops); ... } * Target.td 有定義一些 operand,但針對地址計算這一類的 operand 而言,一般要目標平台自行定義。 /// PointerLikeRegClass - Values that are designed to have pointer width are /// derived from this. TableGen treats the register class as having a symbolic /// type that it doesn't know, and resolves the actual regclass to use by using /// the TargetRegisterInfo::getPointerRegClass() hook at codegen time. class PointerLikeRegClass { int RegClassKind = Kind; } /// ptr_rc definition - Mark this operand as being a pointer value whose /// register class is resolved dynamically via a callback to TargetInstrInfo. /// FIXME: We should probably change this to a class which contain a list of /// flags. But currently we have but one flag. def ptr_rc : PointerLikeRegClass<0>; def MEMrr : Operand { let PrintMethod = "printMemOperand"; let MIOperandInfo = (ops ptr_rc, ptr_rc); let ParserMatchClass = SparcMEMrrAsmOperand; } def MEMri : Operand { let PrintMethod = "printMemOperand"; let MIOperandInfo = (ops ptr_rc, i32imm); let ParserMatchClass = SparcMEMriAsmOperand; } * [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Sparc/SparcInstrInfo.td|SparcInstrInfo.td]] * [[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/Target.td|Target.td]] * Calling Convention ([[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/TargetCallingConv.td|TargetCallingConv.td]]) * [[http://llvm.org/docs/WritingAnLLVMBackend.html#calling-conventions|Calling Conventions]] * 一般寫在各自的 CallingConv.td。例如: [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/X86/X86CallingConv.td|X86CallingConv.td]]。又或者寫在 RegisterInfo.td。例如: [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Hexagon/HexagonRegisterInfo.td|HexagonRegisterInfo.td]]。 * 定義 callee saved register。 class CalleeSavedRegs { dag SaveList = saves; dag OtherPreserved; } def HexagonCSR : CalleeSavedRegs<(add R16, R17, R18, R19, R20, R21, R22, R23, R24, R25, R26, R27)>; * 根據參數型別,透過特定暫存器傳遞,這是一種 action。[[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/TargetCallingConv.td|TargetCallingConv.td]] 提供多種 action 設定方式。 class CCIfType vts, CCAction A> : CCPredicateAction { list VTs = vts; } class CCAssignToReg regList> : CCAction { list RegList = regList; } CCIfType<[f32,f64], CCAssignToReg<[R0, R1]>> * 可以將數條 action 打包在一起,作為一組 calling convention。例如針對不同返回值,透過不同暫存器傳遞。 class CallingConv actions> { list Actions = actions; bit Custom = 0; } def RetCC_Sparc32 : CallingConv<[ CCIfType<[i32], CCAssignToReg<[I0, I1]>>, CCIfType<[f32], CCAssignToReg<[F0]>>, CCIfType<[f64], CCAssignToReg<[D0]>> ]>; * Intrinsics * [[http://lists.llvm.org/pipermail/llvm-dev/2015-October/091684.html|[llvm-dev] add intrinsic function support for customized backend]] * [[http://lists.llvm.org/pipermail/llvm-dev/2017-June/114321.html|[llvm-dev] ADDING A CUSTOM INSTRINSIC]] * 先在 LLVM 加上支持,再修改 Clang 處理 intrinsic。 __builtin_yyy -> @llvm.xxx.yyy -> yyy * Clang * 各平台的 intrinsic 各自定義在 ''tools/clang/include/clang/Basic/'' 底下。例如: [[https://github.com/llvm-mirror/clang/blob/master/include/clang/Basic/BuiltinsARM.def|BuiltinsARM.def]]。會用到 [[https://github.com/llvm-mirror/clang/blob/master/include/clang/Basic/Builtins.def|Builtins.def]] 定義的型別和屬性。 BUILTIN(__builtin_atan2 , "ddd" , "Fnc" ) builtin 函式名 返回和參數型別 builtin 屬性 * [[https://clang.llvm.org/doxygen/CodeGenFunction_8h_source.html|CodeGenFunction.h]]: 宣告 Clang 遇到 builtin/intrinsic 時要調用的 Emit 函式。例如: ''EmitARMBuiltinExpr''。 * [[https://clang.llvm.org/doxygen/CGBuiltin_8cpp_source.html|CGBuiltin.cpp]]: 定義 Clang 遇到 builtin/intrinsic 時要調用的 Emit 函式。例如: ''EmitARMBuiltinExpr''。[[https://clang.llvm.org/doxygen/CGBuiltin_8cpp.html#a1d6741b01869d4dc731f0e1211d0e4cd|EmitTargetArchBuiltinExpr()]] 調用定義前述的 Emit 函式。 * [[https://clang.llvm.org/doxygen/SemaChecking_8cpp_source.html|SemaChecking.cpp]]: 如有需要,添加對 intrinsic 參數的檢查。例如: ''Sema::CheckARMBuiltinFunctionCall''。 * LLVM * 在 ''include/llvm/IR/'' 底下平台各自定義 LLVM IR 階段會產生的 intrinsic,以 llvm.xxx 開頭,其中 xxx 為目標平台名稱。例如: [[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/IR/IntrinsicsHexagon.td|IntrinsicsHexagon.td]]。會用到 [[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/IR/Intrinsics.td|Intrinsics.td]] 中定義的型別和屬性。注意!在 [[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/IR/Intrinsics.td|Intrinsics.td]] 最後要引用自己定義的 *.td 檔。 def int_xxx_foo : Intrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_i32_ty], [IntrReadArgMem]>; 返回型別 參數型別 屬性 * 在 ''lib/Target/'' 底下各自的目錄定義如何將 LLVM IR 中的 intrinsic 對應到實際的指令。例如: [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Hexagon/HexagonIntrinsics.td|HexagonIntrinsics.td]]。 let isPseudo = 1 in { def FOO : PseudoI<(outs i32mem:$dst), (ins i32mem:$src1, i32mem:$src2,), [(set i32mem:$dst, (int_xxx_foo i32mem:$src1, i32mem:$src2))]>; } * [[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/TargetSelectionDAG.td|TargetSelectionDAG.td]] * SDNode class SDNode props = [], string sdclass = "SDNode"> : SDPatternOperator { string Opcode = opcode; string SDClass = sdclass; let Properties = props; SDTypeProfile TypeProfile = typeprof; } def imm : SDNode<"ISD::Constant" , SDTIntLeaf , [], "ConstantSDNode">; * opcode 來自於 [[http://llvm.org/doxygen/ISDOpcodes_8h_source.html|ISDOpcodes.h]]。 * PatFrag/OutPatFrag/PatLeaf/ImmLeaf class PatFrag : SDPatternOperator { dag Operands = ops; dag Fragment = frag; code PredicateCode = pred; code ImmediateCode = [{}]; SDNodeXForm OperandTransform = xform; } * Pattern/Pat class Pattern resultInstrs> { dag PatternToMatch = patternToMatch; list ResultInstrs = resultInstrs; list Predicates = []; // See class Instruction in Target.td. int AddedComplexity = 0; // See class Instruction in Target.td. } class Pat : Pattern; 匹配 輸出 * dag 是 TableGen 自定義型別。Pat 會匹配 pattern,輸出 result。dag 可以使用的 operator 來自 [[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/Target.td|Target.td]] (如: COPY_TO_REGCLASS) 或是 [[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/TargetSelectionDAG.td|TargetSelectionDAG.td]] (如: add)。 * [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Hexagon/HexagonIntrinsics.td|HexagonIntrinsics.td]] class T_I_pat : Pat <(IntID imm:$Is), // 匹配 (MI imm:$Is)>; // 輸出 * [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Hexagon/HexagonPatterns.td|HexagonPatterns.td]] def: Pat<(i64 (add I64:$Rs, I64:$Rt)), (A2_addp I64:$Rs, I64:$Rt)>; 匹配 輸出 ==== Register Pair ==== * 支持 quad-register ([[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/AMDGPU/SIRegisterInfo.td|SIRegisterInfo.td]])。 class RegisterTuples Indices, list Regs> { // SubRegs - N lists of registers to be zipped up. Super-registers are // synthesized from the first element of each SubRegs list, the second // element and so on. list SubRegs = Regs; // SubRegIndices - N SubRegIndex instances. This provides the names of the // sub-registers in the synthesized super-registers. list SubRegIndices = Indices; } // VGPR_32 共有 VGPR0 ~ VGPR255 個暫存器。 def VGPR_32 : RegisterClass<"AMDGPU", [i32, f32, i16, f16, v2i16, v2f16], 32, (add (sequence "VGPR%u", 0, 255))> { let AllocationPriority = 1; let Size = 32; } // VGPR 128-bit registers def VGPR_128 : RegisterTuples<[sub0, sub1, sub2, sub3], [(add (trunc VGPR_32, 253)), // VGPR0 ~ VGPR252 (add (shl VGPR_32, 1)), // VGPR1 ~ VGPR255 (add (shl VGPR_32, 2)), // VGPR2 ~ VGPR255 (add (shl VGPR_32, 3))]>; // VGPR3 ~ VGPR255 def VReg_128 : RegisterClass<"AMDGPU", [v4i32, v4f32, v2i64, v2f64], 32, (add VGPR_128)> { let Size = 128; // Requires 4 v_mov_b32 to copy let CopyCost = 4; let AllocationPriority = 4; } * trunc 將第 N 個元素以後的元素從集合中截去。 * shl 移除集合中前 N 個元素。 * decimate 從集合中挑出所有第 N 個元素。 // Calling convention for leaf functions def CC_AMDGPU_Func : CallingConv<[ // 針對要映射到 quad-register 的型別,調用相應的處理函式。 CCIfType<[i64, f64, v2i32, v2f32, v4i32, v4f32, v8i32, v8f32, v16i32, v16f32, v2i64, v2f64], CCCustom<"allocateVGPRTuple">>, CCIfType<[v4i32, v4f32, v2i64, v2f64], CCAssignToStack<16, 4>>, ]>; ==== Subtarget ==== * 區分不同 subtaregt 的指令。Predicates 和 Requires 只對有寫 pattern match 的指令起作用。Predicates 是 Pat 父類別 Pattern 的成員,Requires 是獨立的類別。 def HasV5T : Predicate<"HST->hasV5TOps()">, AssemblerPredicate<"ArchV5">; let Predicates = [HasV5T] in { def: Pat<(f32 (fminnum F32:$Rs, F32:$Rt)), (F2_sfmin F32:$Rs, F32:$Rt)>; def: Pat<(f32 (fmaxnum F32:$Rs, F32:$Rt)), (F2_sfmax F32:$Rs, F32:$Rt)>; } def: Pat<(sra (add (sra I64:$src, u6_0ImmPred:$u6), 1), (i32 1)), (S2_asr_i_p_rnd DoubleRegs:$src, imm:$u6)>, Requires<[HasV5T]>; ==== InstrMapping ==== 不同版本指令之間的映射關係,例如 Hexagon 的 Dot-New 指令 ([[https://developer.qualcomm.com/download/hexagon/hexagon-dsp-architecture.pdf|Hexagon DSP Architecture]]),可以使用 InstrMapping 表示 ([[https://llvm.org/docs/HowToUseInstrMappings.html|How To Use Instruction Mappings]])。例如我們想建立 add/add.t/add.f 和 sub/sub.t/sub.f 的映射表,以 add/sub 為 key,可以取得不同版本的指令,如: add.t/sub.t 和 add.f/sub.f。 | ^ no pred ^ true ^ false ^ ^ add | add | add.t | add.f | ^ sub | sub | sub.t | sub.f | * 以 Hexagon 為例。[[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Hexagon/Hexagon.td|Hexagon.td]] 定義多個 InstrMapping ([[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/Target.td|Target.td]])。此處定義的 InstrMapping,如: getPredOpcode,會生成為一個函式供人調用。 def getPredOpcode : InstrMapping { // 描述此 mapping 的字串。 let FilterClass = "PredRel"; // add/add.t/add.f 和 sub/sub.t/sub.f 分別有相同的 RowFields。 let RowFields = ["BaseOpcode", "isNVStore", "PNewValue", "isBrTaken", "isNT"]; // add/sub,add.t/sub.t 和 add.f/sub.f 分別有相同的 ColFields。 let ColFields = ["PredSense"]; // 取 ColFields 某值作為 mapping 中的 key。這裡取 add 和 sub 作為 key。 let KeyCol = [""]; // 取 ColFields 某值作為 mapping 中的 value。add 映射到 add.t/add.f,sub 映射到 sub.t/sub.f。 let ValueCols = [["true"], ["false"]]; } * [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Hexagon/HexagonInstrFormats.td|HexagonInstrFormats.td]] 在 InstHexagon 中添加 InstrMapping 所需要的欄位。 class InstHexagon pattern, string cstr, InstrItinClass itin, IType type> : Instruction { let Namespace = "Hexagon"; // Fields used for relation models. string isNT = ""; // set to "true" for non-temporal vector stores. string BaseOpcode = ""; string PredSense = ""; string PNewValue = ""; string isBrTaken = !if(isTaken, "true", "false"); // Set to "true"/"false" for jump instructions let PredSense = !if(isPredicated, !if(isPredicatedFalse, "false", "true"), ""); let PNewValue = !if(isPredicatedNew, "new", ""); let NValueST = !if(isNVStore, "true", "false"); let isNT = !if(isNonTemporal, "true", "false"); } * 此處搭配 TSFlag ([[https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Target/Target.td|Target.td]]) 設置 RowFields 和 ColFields。 * [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Hexagon/HexagonDepInstrInfo.td|HexagonDepInstrInfo.td]] 定義彼此互有關聯的指令。 class HInst : InstHexagon; def A2_add : HInst< (outs IntRegs:$Rd32), (ins IntRegs:$Rs32, IntRegs:$Rt32), "$Rd32 = add($Rs32,$Rt32)", tc_548f402d, TypeALU32_3op>, Enc_5ab2be, PredNewRel, ImmRegRel { let Inst{7-5} = 0b000; let Inst{13-13} = 0b0; let Inst{31-21} = 0b11110011000; let hasNewValue = 1; let opNewValue = 0; let CextOpcode = "A2_add"; let InputType = "reg"; let BaseOpcode = "A2_add"; let isCommutable = 1; let isPredicable = 1; } def A2_paddf : HInst< (outs IntRegs:$Rd32), (ins PredRegs:$Pu4, IntRegs:$Rs32, IntRegs:$Rt32), "if (!$Pu4) $Rd32 = add($Rs32,$Rt32)", tc_1b6011fb, TypeALU32_3op>, Enc_ea4c54, PredNewRel, ImmRegRel { let Inst{7-7} = 0b1; let Inst{13-13} = 0b0; let Inst{31-21} = 0b11111011000; let isPredicated = 1; let isPredicatedFalse = 1; let hasNewValue = 1; let opNewValue = 0; let CextOpcode = "A2_add"; let InputType = "reg"; let BaseOpcode = "A2_add"; } def A2_paddt : HInst< (outs IntRegs:$Rd32), (ins PredRegs:$Pu4, IntRegs:$Rs32, IntRegs:$Rt32), "if ($Pu4) $Rd32 = add($Rs32,$Rt32)", tc_1b6011fb, TypeALU32_3op>, Enc_ea4c54, PredNewRel, ImmRegRel { let Inst{7-7} = 0b0; let Inst{13-13} = 0b0; let Inst{31-21} = 0b11111011000; let isPredicated = 1; let hasNewValue = 1; let opNewValue = 0; let CextOpcode = "A2_add"; let InputType = "reg"; let BaseOpcode = "A2_add"; } * [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Hexagon/HexagonInstrInfo.cpp|HexagonInstrInfo.cpp]] 再利用之前定義的 InstrMapping 接口,取得不同版本的的指令。 int HexagonInstrInfo::getCondOpcode(int Opc, bool invertPredicate) const { enum Hexagon::PredSense inPredSense; inPredSense = invertPredicate ? Hexagon::PredSense_false : Hexagon::PredSense_true; int CondOpcode = Hexagon::getPredOpcode(Opc, inPredSense); if (CondOpcode >= 0) // Valid Conditional opcode/instruction return CondOpcode; llvm_unreachable("Unexpected predicable instruction"); } 在 Hexagon Dot-New 指令 ([[https://developer.qualcomm.com/download/hexagon/hexagon-dsp-architecture.pdf|Hexagon DSP Architecture]]) 的應用。原本 VLIW 的一個 bundle (packet) 內的指令不會有依賴關係,為了增加 bundle 內的指令個數,引入 Dot-New 指令,使得 bundle 內的指令可以有依賴關係。 * ''isLegalToPacketizeTogether'' 檢視 SUI 是否能和 SUJ 做成一個 packet。 bool HexagonPacketizerList::isLegalToPacketizeTogether(SUnit *SUI, SUnit *SUJ) { // SUJ 是當前 packet 內已存在的指令,檢查 SUI 是否和 SUJ 有依賴,以及該種依賴是否可以處理, // 使得 SUI 和 SUJ 可以放在同一個 packet。 for (unsigned i = 0; i < SUJ->Succs.size(); ++i) { if (SUJ->Succs[i].getSUnit() != SUI) continue; // SUI 和 SUJ 是何種依賴? SDep::Kind DepType = SUJ->Succs[i].getKind(); // For instructions that can be promoted to dot-new, try to promote. if (DepType == SDep::Data) { if (canPromoteToDotNew(I, SUJ, DepReg, II, RC)) { if (promoteToDotNew(I, DepType, II, RC)) { PromotedToDotNew = true; if (cannotCoexist(I, J)) FoundSequentialDependence = true; continue; } } if (HII->isNewValueJump(I)) continue; } * ''promoteToDotNew'' 透過 InstrMapping 生成的接口,返回當前指令對應的 Dot-New 版本。 // Promote an instruction to its .new form. At this time, we have already // made a call to canPromoteToDotNew and made sure that it can *indeed* be // promoted. bool HexagonPacketizerList::promoteToDotNew(MachineInstr &MI, SDep::Kind DepType, MachineBasicBlock::iterator &MII, const TargetRegisterClass* RC) { assert (DepType == SDep::Data); int NewOpcode; if (RC == &Hexagon::PredRegsRegClass) NewOpcode = HII->getDotNewPredOp(MI, MBPI); else NewOpcode = HII->getDotNewOp(MI); MI.setDesc(HII->get(NewOpcode)); return true; } ===== Writing Backend ===== * 此節只簡略介紹開發後端會碰到的主要幾個檔案,不深入研究。細節可以分到其它地方。 * [[http://llvm.org/docs/WritingAnLLVMBackend.html|Writing an LLVM Backend]] * [[https://github.com/Jonathan2251/lbd.git|lbd: llvm backend document]] * [[http://ccckmit.github.io/co/htm/cpu0.html|開放電腦計畫 -- 計算機硬體結構]] * 參考 [[https://github.com/lowRISC/riscv-llvm| RISC-V LLVM]] 的 commit 順序。 先介紹後端主要目錄結構。後端代碼位於 ''include/llvm'' 和 ''lib'' 底下幾個目錄: * CodeGen: 通用代碼生成算法。 * MC: 匯編/反匯編相關。 * TableGen: ''tblgen'' 代碼。LLVM 利用 ''tblgen'' 讀取目標平台 *.td 檔生成 instruction selector,instruction scheduling,register allocation 和 assembly printing。 $ cd lib/Target/X86 $ llvm-tblgen X86.td -print-enums -class=Register -I ../../../include/ $ llvm-tblgen X86.td -print-enums -class=Instruction -I ../../../include/ * [[http://llvm.org/docs/TableGen/index.html|TableGen]] * ''tblgen'' 語法近似 C++ template。*.td 檔主體為 record,record 分為兩類: class 和 definition。class 是模板 record,definition 是實例 record。 * [[http://llvm.org/docs/TableGen/LangIntro.html|TableGen Language Introduction]] $ cat insns.td // 指令基類 class Insn MajOpc, bit MinOpc> { bits<32> insnEncoding; // 指令編碼,長度 32 位。 let insnEncoding{15-12} = MajOpc; // 第 15 至 12 位為 MajOpc let insnEncoding{11} = MinOpc; // 第 11 位為 MinOpc } multiclass RegAndImmInsn opcode> { def rr : Insn<0x00, 0>; // MajOpc 皆為 0x00, MinOpc 分別為 0 和 1。 def ri : Insn<0x00, 1>; } // SUB 指令 def SUB: Insn<0x00, 0>; // MajOpc = 0x00, MinOpc = 0 // ADD 指令。運算元可以是暫存器 + 暫存器,或是暫存器 + 立即數。 defm ADD : RegAndImmInsn<0x01>; $ llvm-tblgen -print-records inst.td * Target: 不同後端有各自的子目錄。 這裡介紹開發後端會碰到的主要幾個檔案。參考文檔: * [[http://llvm.org/docs/WritingAnLLVMBackend.html|Writing an LLVM Backend]] * [[http://llvm.org/devmtg/2012-04-12/Slides/Workshops/Anton_Korobeynikov.pdf|Tutorial: Building a backend in 24 hours (2012) 28 頁]] * [[http://people.cs.nctu.edu.tw/~chenwj/slide/LLVM/LLVM-how-to-add-backend.txt]] 以 X86 為例,[[http://llvm.org/doxygen/X86TargetMachine_8h_source.html|X86TargetMachine.h]] 和 [[https://llvm.org/svn/llvm-project/llvm/trunk/lib/Target/X86/X86.td|X86.td]] 是兩個最主要的檔案。上層平台無關代碼生成算法透過 X86TargetMachine.h 的接口得到底層平台相關資訊。X86.td 描述 ISA 擴展和處理器家族,並 include 其它 *.td 檔。 * [[http://llvm.org/docs/WritingAnLLVMBackend.html#target-machine|Target Machine]] * [[http://llvm.org/doxygen/X86TargetMachine_8h_source.html|X86TargetMachine.h]]: 後端的主要類別,負責膠合其它後端類別並負責控制後端管線。 * [[http://llvm.org/docs/WritingAnLLVMBackend.html#register-set-and-register-classes|Register Set and Register Classes]] * [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/X86/X86RegisterInfo.td|X86RegisterInfo.td]]: 定義目標平台的暫存器和暫存器組,暫存器組指對某一類指令而言,該暫存器組會被以相同方式對待。 * [[http://llvm.org/doxygen/X86RegisterInfo_8h_source.html|X86RegisterInfo.h]]: 引入 ''X86RegisterInfo.td'' 生成的 ''X86GenRegisterInfo.inc''。提供關於暫存器組的各項資訊,諸如: 被調用方保存暫存器,暫存器分配次序等等。 * [[http://llvm.org/docs/WritingAnLLVMBackend.html#instruction-set|Instruction Set]] * [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/X86/X86.td|X86.td]]: 描述 ISA 擴展和處理器家族。此檔會 include 其它所有 *.td 檔。 * [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/X86/X86InstrFormats.td|X86InstrFormats.td]]: 描述如何從 DAG 對應到目標指令。參閱 ''include/llvm/Target/Target.td'',從 DAG (MI) 轉成目標匯編。 * [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/X86/X86InstrInfo.td|X86InstrInfo.td]]: ''include/llvm/Target/TargetSelectionDAG.td'' 定義各種 SDNode 型別。 * [[http://llvm.org/docs/WritingAnLLVMBackend.html#instruction-selector|Instruction Selector]] * [[http://llvm.org/doxygen/X86ISelDAGToDAG_8cpp_source.html|X86ISelDAGToDAG.cpp]]: 負責指令選取。 * [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/X86/X86CallingConv.td|X86CallingConv.td]]: 調用約定。 * [[http://llvm.org/doxygen/X86ISelLowering_8h_source.html|X86ISelLowering.h]]: 負責將 LLVM IR 降轉 (lowering) 成 Selection DAG,以便後續選取指令。 * ''X86ISelLowering.h'' 繼承 [[http://llvm.org/doxygen/classllvm_1_1TargetLowering.html|TargetLowering]] 實現回調,並定義平台相關的 SDNode。 * [[http://llvm.org/docs/WritingAnLLVMBackend.html#subtarget-support|Subtarget Support]] * [[http://llvm.org/doxygen/X86Subtarget_8h_source.html|X86Subtarget.h]]: 一個 TargetMachine 可能有多個子目標,控制不同子目標的特性。 ====== JIT ====== * MCJIT 會被 ORCJIT 取代。 * [[https://www.youtube.com/watch?v=hILdR8XRvdQ|2016 LLVM Developers’ Meeting: ORC -- LLVM's Next Generation of JIT API]] * [[http://eli.thegreenplace.net/2017/adventures-in-jit-compilation-part-1-an-interpreter/|Adventures in JIT compilation: Part 1 - an interpreter]] * [[http://eli.thegreenplace.net/2017/adventures-in-jit-compilation-part-2-an-x64-jit/|Adventures in JIT compilation: Part 2 - an x64 JIT]] ====== 其它特性 ====== 這裡放感興趣的特性。 * [[Exception handling]] * [[https://llvm.org/docs/ExceptionHandling.html#exception-handling-support-on-the-target|Exception Handling support on the target]] * [[http://llvm.org/devmtg/2011-09-16/EuroLLVM2011-ExceptionHandling.pdf|The new LLVM exception handling scheme]] * [[http://blog.llvm.org/2011/11/llvm-30-exception-handling-redesign.html|LLVM 3.0 Exception Handling Redesign]] * 在後端搜尋關鍵字 EH 和 CFI。 * [[http://llvm.org/doxygen/classllvm_1_1MCCFIInstruction.html|MCCFIInstruction]] * MCCFIInstruction::createDefCfaOffset: .cfi_def_cfa_offset * MCCFIInstruction::createOffset: .cfi_offset * [[http://llvm.org/doxygen/X86FrameLowering_8cpp_source.html|X86FrameLowering.cpp]] * 注意 BuildCFI。 * 分有無 FP 的情況。基本上在保存 callee-saved 暫存器,或是保存 FP 之後,需要生成 CFI。 * 後端 TargetLowering 必須實現 ''getExceptionPointerRegister'' 和 ''getExceptionSelectorRegister''。參考 [[http://llvm.org/doxygen/X86ISelLowering_8cpp_source.html|X86ISelLowering.cpp]]。例外處理需要傳遞兩個指針,一個指向例外本身,另一個指向例外的型別。前者由 ''getExceptionPointerRegister'' 傳遞,後者由 ''getExceptionSelectorRegister'' 傳遞。 * 關於 ''EH_RETURN'',以 Mips 為例。參考 [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Mips/MipsInstrInfo.td|MipsInstrInfo.td]] 的註解: // Exception handling related node and instructions. // The conversion sequence is: // ISD::EH_RETURN -> MipsISD::EH_RETURN -> // MIPSeh_return -> (stack change + indirect branch) * 在 ''MipsTargetLowering::lowerEH_RETURN'' ([[https://llvm.org/doxygen/MipsISelLowering_8cpp_source.html|MipsISelLowering.cpp]]) 將 ''ISD::EH_RETURN'' 替換成 ''MipsISD::EH_RETURN''。''ISD::EH_RETURN'' 的 offset 和 handler 選擇可用的暫存器傳遞,''MipsTargetLowering::lowerEH_RETURN'' 選擇使用暫存器 V0 和 V1 傳遞。 * ''MipsDAGToDAGISel::Select'' ([[https://llvm.org/doxygen/MipsISelDAGToDAG_8cpp_source.html|MipsDAGToDAGISel.cpp]]) 將 ''MipsISD::EH_RETURN'' 匹配成 ''MIPSeh_return32''/''MIPSeh_return64''。''MIPSeh_return32''/''MIPSeh_return64'' 是在 [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Mips/MipsInstrInfo.td|MipsInstrInfo.td]] 定義的偽指令。 def SDT_MipsEHRET : SDTypeProfile<0, 2, [SDTCisInt<0>, SDTCisPtrTy<1>]>; def MIPSehret : SDNode<"MipsISD::EH_RETURN", SDT_MipsEHRET, [SDNPHasChain, SDNPOptInGlue, SDNPVariadic]>; let Uses = [V0, V1], isTerminator = 1, isReturn = 1, isBarrier = 1, isCTI = 1 in { def MIPSeh_return32 : MipsPseudo<(outs), (ins GPR32:$spoff, GPR32:$dst), [(MIPSehret GPR32:$spoff, GPR32:$dst)]>; // lowerEH_RETURN 生成 (MipsISD::EH_RETURN V0, V1) // 和此處需要一致。 def MIPSeh_return64 : MipsPseudo<(outs), (ins GPR64:$spoff, GPR64:$dst), [(MIPSehret GPR64:$spoff, GPR64:$dst)]>; } * ''MipsSEInstrInfo::expandEhReturn'' ([[https://llvm.org/doxygen/MipsSEInstrInfo_8cpp_source.html|MipsSEInstrInfo.cpp]]) 擴展偽指令 ''MIPSeh_return32''/''MIPSeh_return64'',並生成指令,用 V0 調整棧,並將 V1 作為返回地址。 * 關於 ''EH_RETURN'',以 XCore 為例。於 [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/XCore/XCoreInstrInfo.td|XCoreInstrInfo.td]] 定義 ''EH_RETURN'' 偽指令。於 [[https://llvm.org/doxygen/XCoreISelLowering_8h_source.html|XCoreISelLowering.h]] 定義 ''XCoreISD::EH_RETURN''。 * 於 ''XCoreTargetLowering::LowerEH_RETURN'' ([[https://llvm.org/doxygen/XCoreISelLowering_8cpp_source.html|XCoreISelLowering.cpp]]) 將 ''ISD::EH_RETURN'' 轉成 ''XCoreISD::EH_RETURN'',並將 offset 和 handler 保存至暫存器 R2 和 R3。''__builtin_eh_return'' 在 ''_Unwind_RaiseException'' 被調用,其中 caller-saved 暫存器 R0 和 R1 已被 personality function 用於傳參,因此這裡使用暫存器 R2 和 R3 傳遞 offset 和 handler。 ''XCoreDAGToDAGISel::Select'' ([[https://llvm.org/doxygen/XCoreISelDAGToDAG_8cpp_source.html|XCoreISelDAGToDAG.cpp]]) 將 ''XCoreISD::EH_RETURN'' 替換成 ''EH_RETURN'' 偽指令。 * ''XCoreFrameLowering::emitEpilogue'' ([[https://llvm.org/doxygen/XCoreFrameLowering_8cpp_source.html|XCoreFrameLowering.cpp]]) 擴展偽指令 ''XCore::EH_RETURN'',並生成指令,用 R2 調整棧,並將 R3 作為返回地址。 * 關於 ''EH_RETURN'',以 Hexagon 為例。 * 於 [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Hexagon/HexagonPseudo.td|HexagonPseudo.td]] 定義 ''EH_RETURN_JMPR'' 偽指令,於 [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/Hexagon/HexagonPatterns.td|HexagonPatterns.td]] 定義 pattern。 * ''HexagonTargetLowering::LowerEH_RETURN'' ([[https://llvm.org/doxygen/HexagonISelLowering_8cpp_source.html|HexagonISelLowering.cpp]]) 將 ''ISD::EH_RETURN'' 轉成 ''HexagonISD::EH_RETURN'',並將 offset 存到暫存器 R28,handler 存到暫存器 R30 所指向的內存。 * ''HexagonDAGToDAGISel::Select'' ([[https://llvm.org/doxygen/HexagonISelDAGToDAG_8cpp_source.html|HexagonISelDAGToDAG.cpp]]) 將 ''HexagonISD::EH_RETURN'' 替換成 ''EH_RETURN_JMPR'' 偽指令。 * ''HexagonFrameLowering::insertEpilogueInBlock'' ([[https://llvm.org/doxygen/HexagonFrameLowering_8cpp_source.html|HexagonFrameLowering.cpp]]) 替換 ''EH_RETURN_JMPR'' 偽指令,並生成指令,用 R28 調整棧,並將 R30 所存的值作為返回地址。 * 關於 ''EH_RETURN'',以 X86 為例。 * 於 [[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/X86/X86InstrInfo.td|X86InstrInfo.td]] 為 ''X86ISD::EH_RETURN'' 定義 ''X86ehret'' SDNode。[[https://github.com/llvm-mirror/llvm/blob/master/lib/Target/X86/X86InstrCompiler.td|X86InstrCompiler.td]] 定義偽指令 ''EH_RETURN''。 * ''X86TargetLowering::LowerEH_RETURN'' ([[https://llvm.org/doxygen/X86ISelLowering_8cpp_source.html|X86ISelLowering.cpp]]) 將 ''ISD::EH_RETURN'' 轉成 ''X86ISD::EH_RETURN'',並將 handler 存在 frame + offset 偏移處,且將 frame + offset 存在 ECX。 * ''X86DAGToDAGISel::Select'' 將 ''X86ISD::EH_RETURN'' 替換成 ''X86::EH_RETURN'' 偽指令。 * ''X86ExpandPseudo::ExpandMI'' ([[https://llvm.org/doxygen/X86ExpandPseudo_8cpp_source.html|X86ExpandPseudo.cpp]]) 替換 ''X86::EH_RETURN'' 偽指令,並生成指令,將棧指針調整至 ECX 所指的位址。 * 關於 ''emitPrologue''/''emitEpilogue'',以 Mips 為例。可以透過 [[http://llvm.org/doxygen/MipsMachineFunction_8h.html|MipsFunctionInfo]] 或是 [[http://llvm.org/doxygen/structllvm_1_1MachineFunctionInfo.html|MachineFunctionInfo]] 定義的 ''callsEHReturn'' 得知函式是否有調用 eh_return。 * ''MipsSEFrameLowering::emitPrologue'' ([[http://llvm.org/doxygen/MipsSEFrameLowering_8cpp_source.html|MipsSEFrameLowering.cpp]])。注意!此處需要為例外處理需要用到的暫存器生成對應的 cfi。 if (MipsFI->callsEhReturn()) { // Insert instructions that spill eh data registers. for (int I = 0; I < 4; ++I) { if (!MBB.isLiveIn(ABI.GetEhDataReg(I))) MBB.addLiveIn(ABI.GetEhDataReg(I)); TII.storeRegToStackSlot(MBB, MBBI, ABI.GetEhDataReg(I), false, MipsFI->getEhDataRegFI(I), RC, &RegInfo); } // Emit .cfi_offset directives for eh data registers. for (int I = 0; I < 4; ++I) { int64_t Offset = MFI.getObjectOffset(MipsFI->getEhDataRegFI(I)); unsigned Reg = MRI->getDwarfRegNum(ABI.GetEhDataReg(I), true); unsigned CFIIndex = MF.addFrameInst( MCCFIInstruction::createOffset(nullptr, Reg, Offset)); BuildMI(MBB, MBBI, dl, TII.get(TargetOpcode::CFI_INSTRUCTION)) .addCFIIndex(CFIIndex); } } * ''MipsSEFrameLowering::emitEpilogue'' ([[http://llvm.org/doxygen/MipsSEFrameLowering_8cpp_source.html|MipsSEFrameLowering.cpp]])。 if (MipsFI->callsEhReturn()) { const TargetRegisterClass *RC = ABI.ArePtrs64bit() ? &Mips::GPR64RegClass : &Mips::GPR32RegClass; // Find first instruction that restores a callee-saved register. MachineBasicBlock::iterator I = MBBI; for (unsigned i = 0; i < MFI.getCalleeSavedInfo().size(); ++i) --I; // Insert instructions that restore eh data registers. for (int J = 0; J < 4; ++J) { TII.loadRegFromStackSlot(MBB, I, ABI.GetEhDataReg(J), MipsFI->getEhDataRegFI(J), RC, &RegInfo); } } * ''MipsSEFrameLowering::determineCalleeSaves'' ([[http://llvm.org/doxygen/MipsSEFrameLowering_8cpp_source.html|MipsSEFrameLowering.cpp]]) 會調用 ''MipsFunctionInfo::createEhDataRegsFI'' ([[http://llvm.org/doxygen/MipsMachineFunction_8cpp_source.html|MipsMachineFunction.cpp]]) 為例外處理使用到的暫存器,創建 spill 空間。''emitPrologue''/''emitEpilogue'' 依據該位置生成 load/store 代碼。 * 關於 ''emitPrologue''/''emitEpilogue'',以 XCore 為例。 * ''XCoreFrameLowering::emitPrologue'' ([[http://llvm.org/doxygen/XCoreFrameLowering_8cpp_source.html|XCoreFrameLowering.cpp]])。 if (XFI->hasEHSpillSlot()) { // The unwinder requires stack slot & CFI offsets for the exception info. // We do not save/spill these registers. const Function *Fn = &MF.getFunction(); const Constant *PersonalityFn = Fn->hasPersonalityFn() ? Fn->getPersonalityFn() : nullptr; SmallVector SpillList; GetEHSpillList(SpillList, MFI, XFI, PersonalityFn, MF.getSubtarget().getTargetLowering()); assert(SpillList.size()==2 && "Unexpected SpillList size"); EmitCfiOffset(MBB, MBBI, dl, TII, MRI->getDwarfRegNum(SpillList[0].Reg, true), SpillList[0].Offset); EmitCfiOffset(MBB, MBBI, dl, TII, MRI->getDwarfRegNum(SpillList[1].Reg, true), SpillList[1].Offset); } * ''XCoreFrameLowering::emitPrologue'' ([[http://llvm.org/doxygen/XCoreFrameLowering_8cpp_source.html|XCoreFrameLowering.cpp]])。 if (RetOpcode == XCore::EH_RETURN) { // 'Restore' the exception info the unwinder has placed into the stack // slots. const Function *Fn = &MF.getFunction(); const Constant *PersonalityFn = Fn->hasPersonalityFn() ? Fn->getPersonalityFn() : nullptr; SmallVector SpillList; GetEHSpillList(SpillList, MFI, XFI, PersonalityFn, MF.getSubtarget().getTargetLowering()); RestoreSpillList(MBB, MBBI, dl, TII, RemainingAdj, SpillList); // Return to the landing pad. unsigned EhStackReg = MBBI->getOperand(0).getReg(); unsigned EhHandlerReg = MBBI->getOperand(1).getReg(); BuildMI(MBB, MBBI, dl, TII.get(XCore::SETSP_1r)).addReg(EhStackReg); BuildMI(MBB, MBBI, dl, TII.get(XCore::BAU_1r)).addReg(EhHandlerReg); MBB.erase(MBBI); // Erase the previous return instruction. return; } * ''XCoreFrameLowering::determineCalleeSaves'' ([[http://llvm.org/doxygen/XCoreFrameLowering_8cpp_source.html|XCoreFrameLowering.cpp]]) 會調用 ''XCoreFunctionInfo::createEHSpillSlot'' ([[http://llvm.org/doxygen/XCoreMachineFunctionInfo_8cpp_source.html|XCoreMachineFunctionInfo.cpp]]) 為例外處理使用到的暫存器,創建 spill 空間。''emitPrologue''/''emitEpilogue'' 依據該位置生成 load/store 代碼。 * [[wp>Coroutine]] * [[http://llvm.org/docs/Coroutines.html|Coroutines in LLVM]] * [[https://www.youtube.com/watch?v=8C8NnE1Dg4A|CppCon 2016: Gor Nishanov “C++ Coroutines: Under the covers"]] * [[https://www.youtube.com/watch?v=Ztr8QvMhqmQ|2016 LLVM Developers’ Meeting: G. Nishanov “LLVM Coroutines”]] * [[http://llvm.org/devmtg/2016-11/Slides/Nishanov-LLVMCoroutines.pdf|LLVM Coroutines (2016)]] * [[http://electronic-blue.herokuapp.com/blog/2012/06/coroutine-an-introduction/|Coroutine: 入門篇]] * function 可以被認為是 coroutine 的一個特化版本。coroutine 內可以有多個暫停點 (yield/suspend),當執行到暫停點時,控制流會回到 caller。caller 可以選擇回復 (resume) coroutine 的執行,coroutine 會從上一次的暫停點開始執行。function 就是沒有暫停點的 coroutine。 * [[wp>Software pipelining]] * 將多個 loop iteration 排進流水線,最大化 ILP。需要考慮 data dependency (data dependency graph) 和硬件資源的限制 (resource reservation table)。 * [[ftp://gcc.gnu.org/pub/gcc/summit/2004/Swing%20Modulo%20Scheduling.pdf|Swing Modulo Scheduling for GCC]] * [[http://llvm.org/pubs/2005-06-17-LattnerMSThesis.pdf|AN IMPLEMENTATION OF SWING MODULO SCHEDULING WITH EXTENSIONS FOR SUPERBLOCKS]] * Modulo Scheduling 試圖讓多個 iteration 並行,縮小 iteration 之間的啟動 (initiation) 間距,盡可能填滿 CPU pipeline。 * 建立 Data Dependence Graph (DDG),藉此計算兩個連續的 iteration 啟動之間所需的 cycle,此值稱為 MII (Minimum Initiation Interval)。以 MII 作為 II (Initiation Interval)) 的初始值,試著把所有指令放進 II 區間。如果不行,II 放寬一個 cycle,重複前一個步驟。 * MII 取 Res (Resource constrained) MII 和 Rec (Recurrence constrained) MII 的最大值。 * 最後將原來的 loop 重構成 prologue,kernel 和 epilogue。 * Swing Modulo Scheduling (SMS) 另外考慮到 register pressure。如果 scheduling 過於激進,並行的指令使用超過硬件所能提供的 register,spilling 會發生,抵銷 scheduling 所拿到的效能增長。將相依賴的指令盡可能排在鄰近執行,減小 register 的生命週期,增加 register 的使用率。 * [[http://llvm.org/devmtg/2015-04/slides/Roel-SWP-EuroLLVM2015.pdf|A high-level implementation of software pipelining in LLVM]] ([[https://www.youtube.com/watch?v=zChslFrJDh4|Video]] [[https://pdfs.semanticscholar.org/61b1/e8ea1a2d0c22917532e2b8788de5ca7f464f.pdf|Paper]]) * [[http://llvm.org/doxygen/MachinePipeliner_8cpp.html|MachinePipeliner.cpp]][(https://reviews.llvm.org/D16829)] * LibCall * [[http://llvm.org/doxygen/RuntimeLibcalls_8h.html|RuntimeLibcalls.h]] * [[https://gcc.gnu.org/onlinedocs/gccint/Libgcc.html|4 The GCC low-level runtime library]] * 用意在於如果產生目標平台不支援的指令 (和是否開啟 O2 沒有關係),可以調用 libgcc 的實現 [(http://lists.llvm.org/pipermail/llvm-dev/2017-May/113156.html)]。以 [[http://llvm.org/doxygen/LegalizeIntegerTypes_8cpp_source.html|DAGTypeLegalizer::ExpandIntRes_Shift]] 為例 // 如果之前的代碼都不能處理,看是否能調用 libcall。 if (LC != RTLIB::UNKNOWN_LIBCALL && TLI.getLibcallName(LC)) { SDValue Ops[2] = { N->getOperand(0), N->getOperand(1) }; SplitInteger(TLI.makeLibCall(DAG, LC, VT, Ops, isSigned, dl).first, Lo, Hi); return; } // 如果 LC 對應的函式名為 nullptr,嘗試 ExpandShiftWithUnknownAmountBit 是否能加以處理。 if (!ExpandShiftWithUnknownAmountBit(N, Lo, Hi)) llvm_unreachable("Unsupported shift!"); * [[http://llvm.org/doxygen/classllvm_1_1HexagonTargetLowering.html#ab0f312a890f3aa47e480f40c67df30fe|HexagonTargetLowering::HexagonTargetLowering]] 可以看到部分 libcall 被設為 nullptr。 * [[http://llvm.org/doxygen/MathExtras_8h_source.html|MathExtras.h]] 提供 helper function。例如: 判斷常量的二進制是否是連續的 1-bit。 if (countTrailingOnes(Imm) == countPopulation(Imm)) * Peephole Optimizer * [[http://llvm.org/doxygen/PeepholeOptimizer_8cpp_source.html|PeepholeOptimizer.cpp]] 是通用的優化,目標平台透過實現 TargetInstrInfo 的函式供其調用。 * [[http://llvm.org/doxygen/PPCMIPeephole_8cpp_source.html|PPCMIPeephole.cpp]] 是 PowerPC 特定的優化。 ''PPCMIPeephole::simplifyCode()'' 搜尋特定的 MachineInstr 加以優化。 * [[https://llvm.org/devmtg/2017-02-04/Register-Data-Flow-Framework.pptx|Register Data Flow Framework]] * [[http://llvm.org/doxygen/structllvm_1_1rdf_1_1DataFlowGraph_1_1DefStack.html|RDFGraph.h]] * [[http://llvm.org/doxygen/RDFLiveness_8h_source.html|RDFLiveness.h]] * [[http://llvm.org/doxygen/RDFLiveness_8cpp_source.html|RDFLiveness.cpp]] * [[http://llvm.org/doxygen/RDFCopy_8cpp_source.html|RDFCopy.cpp]] * [[http://llvm.org/doxygen/RDFDeadCode_8cpp_source.html|RDFDeadCode.cpp]] ====== 交叉編譯 ====== * [[GCC]] 也有相關資訊,可以考慮加以合併。 * [[https://clang.llvm.org/docs/CrossCompilation.html|Cross-compilation using Clang]] * 參閱 [[https://www.amazon.com/Getting-Started-LLVM-Core-Libraries/dp/1782166920|Getting Started with LLVM Core Libraries]] 第八章。 交叉編譯有底下術語: * host: 運行交叉工具鏈的平台。 * build: 編譯交叉工具鏈的平台。 * target: 運行交叉工具鏈得到的執行檔,其運行的平台。 * multilib: 在系統上同時安裝支援不同 ABI 的函式庫,讓交叉編譯加以鏈結。如: ''/lib/n32'' 和 ''/lib/n64'' 分別是支持 MIPS n32 和 n64 ABI 的函式庫。 * sysroot: 交叉編譯時,從指定的根目錄搜尋所需的標頭檔或函式庫。 交叉工具鏈,除了編譯器,基本牽涉到底下幾個元件: * C 標準函式庫: * [[https://www.gnu.org/software/libc/|The GNU C Library (glibc)]] * C++ 標準函式庫: * [[https://gcc.gnu.org/onlinedocs/libstdc++/|The GNU C++ Library (libstdc++)]] * [[https://libcxx.llvm.org/|"libc++" C++ Standard Library]] * 運行時函式庫: 一般用來實現目標平台硬件不支持的操作。 * [[https://gcc.gnu.org/onlinedocs/gccint/Libgcc.html|4 The GCC low-level runtime library (libgcc)]] * [[https://compiler-rt.llvm.org/|"compiler-rt" runtime libraries]] * 匯編和鏈結器: * [[https://www.gnu.org/software/binutils/|GNU Binutils]] * [[http://llvm.org/docs/CodeGenerator.html#the-mc-layer|The “MC” Layer]] 是 LLVM 負責實現內建匯編器的組件,[[https://lld.llvm.org/|LLD - The LLVM Linker]] 是 LLVM 側的鏈結器。 * ''--target='' ([[http://llvm.org/doxygen/Triple_8h_source.html|Triple.h]]) * triple 格式為 ---。例如: x86_64-apple-macosx10.11.0,--。 * 底下選項用來更加精確的指定目標平台: * ''-march='': 指定指令集架構版本。如: armv4t。此選項同時會設置預設的 CPU。 * ''-mcpu='': 指定 CPU。如: cortex-a8。此選項同時會設置預設的 float-abi。 * ''-mfloat-abi='': 指定軟浮點或是硬浮點,以及 abi。 ====== 測試 ====== 詳細請見 [[http://llvm.org/docs/TestingGuide.html|LLVM Testing Infrastructure Guide]]。 * [[http://llvm.org/docs/CommandGuide/FileCheck.html|FileCheck]] $ ./build.Ninja/bin/llvm-lit llvm/test/CodeGen/* $ ./build.Ninja/bin/llvm-lit llvm/tools/clang/test/CodeGen/* * FileCheck,llvm-lit 這一類開發者使用的工具不會被裝在安裝目錄底下,都放在編譯目錄。 * '';CHECK:'' 匹配指定的字串。多個 '';CHECK:'' 依序匹配。 * '';CHECK:'' 和 '';CHECK-NEXT:'' 搭配可以匹配連續的字串。 * '';CHECK-LABEL:'' * [[http://llvm.org/docs/CommandGuide/FileCheck.html#the-check-label-directive|The “CHECK-LABEL:” directive]] * [[http://lists.llvm.org/pipermail/llvm-dev/2017-March/111657.html|[llvm-dev] CHECK-LABLE or CHECK?]] * FileCheck 會依據 '';CHECK-LABEL:'' 後的字串,把欲匹配的文本切成數個 block。每個 block 再應用各自的 '';CHECK:''。 * 可以使用 regular expression 表示匹配的字符串。 * [[http://llvm.org/docs/CommandGuide/FileCheck.html#filecheck-pattern-matching-syntax|FileCheck Pattern Matching Syntax]] * 可以保存被匹配到字符串,供之後使用。 * [[http://llvm.org/docs/CommandGuide/FileCheck.html#filecheck-variables|FileCheck Variables]] 發行版測試流程請見 [[http://llvm.org/docs/ReleaseProcess.html|How To Validate a New Release]]。一般在預定發行日期前一個月,會公告徵求測試者 ([[http://lists.llvm.org/pipermail/release-testers/2017-June/000423.html|[Release-testers] [5.0.0 Release] Schedule and call for testers]])。 * [[http://lists.llvm.org/pipermail/release-testers/2017-June/000443.html|[Release-testers] LLVM 4.0.1 -final has been tagged]] * [[https://reviews.llvm.org/D42675#994628]] * 測試用例生成盡量使用 ''update_llc_test_checks.py''。 * [[https://reviews.llvm.org/D42515#994726]] ====== 補丁 & 臭蟲 ====== 臭蟲請發送到[[https://bugs.llvm.org/|這裡]] 並先閱讀 [[http://llvm.org/docs/HowToSubmitABug.html|How To Submit A Bug]]。目前發送補丁方式請參照 [[http://llvm.org/docs/Phabricator.html|Code Reviews with Phabricator]]。 * ''git diff -U999999 master > b.patch'' * 在 [[https://reviews.llvm.org/|Phabricator]] 開啟帳戶。目前以 GitHub 帳戶登入。 * 可以選擇訂閱 [[http://lists.llvm.org/mailman/listinfo/llvm-commits|llvm-commits]] 郵件列表。 * 透過 Phabricator 發送的 code review,最好將 llvm-commits 加入 subscriber list。code review 的主題前面加上被修改的模塊名稱,如: ''[Doc]''。 * 確定登入帳戶的 email 有訂閱 llvm-commits。如果沒有的話,Phabricator 以此帳戶發送信件至 llvm-commits 會被 moderate (即受到管制,不能被馬上看到)。 * 可以透過 Phabricator 網頁介面,或是命令行發送 patch。 * 如果 patch 被 approved,但自己沒有 commit 權限,在 comment 上留言表示自己沒有權限,請他人幫忙 commit。 * [[http://llvm.org/docs/DeveloperPolicy.html|LLVM Developer Policy]] * [[http://llvm.org/devmtg/2014-02/slides/ledru-how-to-contribute-to-llvm.pdf|How to contribute to LLVM, Clang, etc]] ====== 外部連結 ====== * [[http://llvm.org/|The LLVM Compiler Infrastructure]] * [[http://llvm.org/devmtg/|LLVM Developers' Meeting]]