# PoCL and OpenCL 重新撰寫文件使其更具有結構,以便知識的傳遞。 * PoCL 編譯 * LLVM 與 PoCL 的關聯 * front-end (`.cl -> .ll`) * Optimize (`.ll -> .ll`) * back-end (`.ll -> .s -> .o`) * PoCL for RISC-V * OpenCL and PoCL * PoCL offline compiler * standalone OpenCL runtime for PoCL kernel function ## PoCL 編譯 最基礎的架構下,你需要一個已經 build 好的 LLVM (最好是以 Debug mode 編譯),以及一個 PoCL 的 source code。 > PoCL 需要 LLVM Debug infomation 去保存編譯過程中的資訊,因為 PoCL 是將 LLVM 當作一個 Library 去使用。當然, LLVM debug build 跟 release build 之間有著巨大的差異。不論是最後生成的 lib 大小或者是 linker 的速度。 * LLVM 的 target 會決定 PoCL 的 target * 用來編譯 PoCL 的 compiler 的 target 會決定 PoCL 的 target 針對以上這兩個條件下,我們可以嘗試去編譯出下面三種不盡相同的 PoCL 以便實驗的進行。 ### X86 > 詳看包好的 script ### RISC-V cross compile > 詳看包好的 script ### RISC-V native > 詳看包好的 script ## POCL Usage ### Native PoCL 預設會運行的環境是一個 homogeneous system。也就是說 Host 與 device 的架構是相同的。 這個狀況下我們可以藉由 OpenCL API 完成 OpenCL program 的編譯與執行。 ``` // 舉例來說,現在有一個 vec_add.c 會去讀取 vecadd.cl // 並且準備好 input 與 config gcc vec_add.c -o vecadd -I<pocl/include/path> -L<pocl/library/path> -lOpenCL ``` 最後執行 vecadd 這個執行檔,PoCL 便會完成 * .cl -> .ll -> .ll -> .s -> .o -> .so then cache the result in cache directory * call dlopen and dlsym to find the requested kernel function * host code invoke kernel function with specify argument ### Non-Native 如果不巧,現在要執行的環境是 heterogeneous system。Host 與 device 使用不同的架構。 > 舉例來說,host 是 x86 而 device 是 RISC-V 這個時候就需要一些流程上的改進才能順利執行這個 OpenCL 程式。 * 使 PoCL 產生出 kernel object file * 產生出與 kernel 相同的 host code * 結合以上兩者編譯後傳送給 device 執行後將結果傳回 host 目前這個版本僅能完成第一點與小部份的第二點。 運作的邏輯如下 * 利用 PoCL 原本就有的 cache system 取出編譯完成的 kernel object 放置於當前目錄 * 手寫一份以特定 kernel function 為目標的 host code * 以 device 的 cross compiler 編譯後給模擬器執行 ## LLVM 與 PoCL 關聯 PoCL 會將 LLVM 作為一個 library 去使用,所以不會直接去呼叫包裝好的執行檔,而是去呼叫 LLVM/clang 內部實作的 function。 ### Front-end 詳看 `pocl/lib/CL/pocl_llvm_build.cc` ``` // In function // int pocl_llvm_build_program(... // ss store option as string std::stringstream ss; // append our option into ss ss << our_option; ``` 這個部份是決定 * OpenCL 相關 option * `.cl -> .ll` ### Optimize 詳看 `pocl/lib/CL/pocl_llvm_wg.cc` * 這個用的 PassManager 實際上是 legacy::PassManager * 這裡運作的方式是先用 string 去標注 pass name,再用 PassRegistry 去查找這個 LLVM pass 的 instance * O3 系列 optmization pass 被標注為 STANDARD_OPTS 後從 PassManagerBuilder 去建構一系列的 Optimization ``` // In function // static PassManager &kernel_compiler_passes(...) ``` ### LLVM CommandLine 詳看 `pocl/lib/CL/pocl_llvm_utils.cc` * 它會在初始化 LLVM 的同時賦予值給相對應的變數 * 還有從 LLVM 取得 generic 的 pass ``` // In function // void InitializeLLVM() O = opts["scalarize-load-store"]; assert(O && "could not find LLVM option 'scalarize-load-store'"); O->addOccurrence(1, StringRef("scalarize-load-store"), StringRef("1"), false); ``` ### Back-end 詳看 `pocl/lib/CL/pocl_llvm_wg.cc` ``` // In function // static TargetMachine *GetTargetMachine(cl_device_id device, Triple &triple) TargetMachine *TM = TheTarget->createTargetMachine( triple.getTriple(), MCPU, StringRef(""), GetTargetOptions(), Reloc::PIC_, CodeModel::Small, CodeGenOpt::Aggressive); ``` 在進行`.ll -> .s`與`.s -> .o`的階段時,PoCL 都會創建一個新的 TargetMachine 去進行這個任務。這個部份主要是可以讓我們任意的在 TargetMachine 中去新增各種 Target feature 的參數已達成去操控 Backend 行為的目的。 舉例來說,我們要手動開啟 V extension 的指令集的話,我們可以新增一個 Target feature `+v` ``` TargetMachine *TM = TheTarget->createTargetMachine( triple.getTriple(), MCPU, StringRef("+v,+i,+m,+a,+f,+d"), GetTargetOptions(), Reloc::PIC_, CodeModel::Small, CodeGenOpt::Aggressive); ``` ## PoCL for RISC-V ## PoCL offline compiler * offline compiler 的目的是產生出一個執行檔可以將 `.cl -> .o` * 並且可以藉由 option 去開關客製化的功能 * 例如新的 LLVM Pass * 例如啟動公司私底下做的加速指令 * offline compiler 基於 PoCL 原始就有提供的 poclcc 並且進行一些改造 ![](https://i.imgur.com/LfTMSYx.png) * 主要分成兩個部份,並且藉由 poclcc 連接起來 * Option parser * 從 opencl-clang 拔過來的 * 基於 LLVM tablegen 的 option parser * OpenCL API * 讀取特定的 .cl * 呼叫綁定的 LLVM 進行後續的 codegen 處理 * 連接兩者的是使用 environment variable * 會這個做的原因是因為 PoCL 原本就使用很多 environment variable 去控制 PoCL 在 codegen 時的行為 ### Usage ``` poclcc -s <OpenCL file path> ``` * 會產生出一個以 OpenCL kernel function 為名的 object file * 如果在同一個 OpenCL file 中有多個 kernel function,就會產生數個相對應的 object file * ### options parser (OpenCL-clang) * 這個專案是由 intel 所維護 * 目的主要是新增一個專門給 OpenCL 用的前端 parser * 運作的方法為 * 撰寫 llvm tblgen 檔案 * 產生 marco * 用 marco 產生更多 marco * 用 switch 去判斷被啟動的是那一些 option * 根據 option 的不同,更新相對應的 environment variable ### OpenCL API (pocl) * PoCL 實作了相當完整的 OpenCL API * 在 offline compiler 可以直接沿用 * 這個部份我們需要修改的有兩個 * 取得 options parser 維護的 environment variable 並且用於 codegen 流程中 * 改造 cache 機制以便使用者取得編譯完成的 object file