2024年4月20日 星期六

我在 SiFive 處理器架構部門四年的經歷

最近在 LinkedIn 讀到了 Jonathan Combs (Intel Atom 架構師)為嚮往處理器架構師的學生所撰寫的文章,一開頭就是讓我覺得很有意思的話:
Very few folks "start" in architecture. For most, it's an earned position. If you get lucky coming straight out of school as an MS or PhD, it's usually due to being within a very specific computer architecture centered research group/program at select universities. If you have to ask if your university is one of those select few, then the answer is "probably not".

字面上看來,處理器架構部門是非常難進去的,除非你在學生時期便在傳統計算機架構名校做過研究(UT Austin, UMich, UIUC, UC Berkley, Cambridge, etc)據我所知,很多在台灣的架構師跟老師都是從 UMich 回來的。文章的下一段便解釋其困難之處:
With the Atom (E-core) team, our performance architects spend most their time in the C/C++ timing model and rely on both their excellent programming skills (lots of CS classes are a must) and their understanding of modern cpu microarchitecture. We additionally have unit/cluster level microarchitects within our architecture team who spend their time thinking in boolean/Verilog and working mainly with the design team. Every one of our microarchitects earned their position as essentially being world class designers who showed acumen for solving large scale microarchitectural problems in the RTL. They were technical leads in their design teams with a penchant for understanding performance and capable of moving to our architecture team to drive the microarchitectural evolution of the CPU.
除了本身的程式設計的能力要優秀外,對處理器的微架構必須非常清楚,教科書上的知識僅僅是門檻。而當代微架構的知識是需要長時間去累積的,試想一顆處理器的設計累積多少人的心血,更別說經歷了多少個世代。短時間拾起這些知識的能力需得要多年的經驗,更別說為下一代處理器架構提供建議。Jonathan 給這些學生具體的建議如下:
If you're hoping to be in one of these positions someday, I suggest you first work your way anywhere into a world class CPU design\validation team. Every place is a great place to start! There are often paths through validation into performance, design into performance, validation into design, as well as paths from design to microarchitecture. Plus there's a ton of microarchitecture that happens within the design organization itself and not micro-managed by "architects". The ideal front-end RTL design team is filled with expert microarchitects.
從一個接近你的領域的職缺開始了解處理器微架構的。例如:電機系的學生可以從 Designer 開始,資工系的學生可以從 Performance Validation 或 Design Verification 開始。這些都是很好的地方開始累積對微架構的知識。

我很幸運地在研究所時期,指導老師讓我去為 RISC-V Vector Extension 開發指令集模擬器及設計處理器架構,並為其開發效能模型。也因為這個經歷,讓我錄取了 SiFive 的處理器架構部門,得以一窺產業界是如何做事的。以下是我在 SiFive 四年的經歷,希望能讓更多人多了解處理器架構部門。

2020年2月3日 星期一

研究所畢業了

研究所畢業了,
陸軍退伍了,
工作要 On Board 了,
能走到這裡真的太不可思議了。

2015 年 11 月。
時處大四的我終於向成大交管系上的研究室請辭研究助理的工作,因自覺無法在這邊繼續精進資訊系統相關的知識。我拿著由中興工程顧問公司的獎學金報名了補習班。

 2017 年 2 月。
各研究所陸續放榜,我也順利地在臺灣大學找到指導教授,承蒙楊佳玲老師不嫌棄我的科系背景下,收了我做研究生。我也在離開學的這段時間,選修了 jserv 老師在成功大學開設的系統軟體課程,藉以精進自己對計算機系統的了解。從我當時的期末個人評量可以看到,這堂課程啟蒙了我對當代電腦系統的認識,不僅是程式碼專案的維護(Coding style, build system, version-control system, ...),還得以一窺網路上開源專案的能力。除了大家一般在意的實作之外,老師也會指派研討會論文給我們撰寫心得報告(直到碩二時,回去翻過往記錄才發現:哇,當初讀的是一篇 TACO 論文啊。)最後,修這堂課還可以體驗在三更半夜裡,老師不斷在 GitHub 發 Code Review 跟 Pull Requests 過來。這麼好的課,不修嗎?

2017 年 6 月。
成大畢業後,我在 Facebook 發了訊息給 jserv 老師,表明想一起研究當時 Java 9 所新增的 AoT (Ahead-of-Time)功能。這也是我第一次去設計數個實驗去測試幾個仍在開發中的程式碼專案 (OpenJDK & OpenJ9),相關成果可見 JCConf 2017 的錄影。也因為這樣的經驗,jserv 老師繼續邀請我加入國立成功大學分散式帳本實驗室 (DLTcollab),並籌備了 dcurl 專案,藉以伺服器硬體架構來加速區塊鏈所需的 PoW(Proof of Work)的運算,相關成果可見 SITCON 2018 的錄影

2018 年 2 月。
我更換到徐慰中老師的實驗室,開始研究新興的計算機架構 RISC-V。

2018 年 7 月。
暑假期間前往老師安排的新竹晶心科技架構設計部實習。我大概花了兩週的時間處理 SPEC Benchmarks,方便工程師進行量分。剩下的六週在前輩的指導下,在公司的處理器模型上實作了實驗性質的分支預測演算法。最後一週也有跟 Designer 報告結果,不過已經忘記有沒有採納了就是。

2018 年 9 月。
回來學校後,我跟徐老師確定了研究題目是想要做 RISC-V Vector 架構的效能分析。也因爲前面在晶心科技的經驗,我們簽訂了產學合作,得已讓我繼續在晶心的處理器模型上進行我的研究。當時進行這個研究題目的難處在於:Vector Architecture 在電腦科學史裏是一種骨灰技術,你不太能向其他研究生一樣單純看最新的 Top Conference Papers。研究過程中,我彷彿在進行某種資訊領域的文藝復興,一邊閱讀著 20、30 年前的文獻,一邊聽著徐老師口述 Cray Research Vector Supercomputer 的 Microarchitecture 細節。當我描繪出心目中的架構設計,同時得與晶心工程師確認是否夠實際;但當我考慮到太多實作細節時,便不曉得該如何在期限內完成研究。這兩邊的權衡,實在很兩難。

2019 年 2 月。
完成 Performance Model 後,便與徐老師設計幾個實驗,整理結果後投稿 3 月在臺灣舉辦的 RISC-V Workshop 發表,成果可見錄影。有趣的事情是,發表結束後的茶會,徐老師就邊吃東西邊跟我說:「你可以畢業了。」老師!可以不要這麼突然嗎!

2019 年 4 月。
我記得我每天都穿著很邋遢,拿著功能最破爛的筆電,跑去社科院圖書館寫作碩士論文,同時準備五月份的論文口試。

2019 年 6 月。
口試順利結束。因為各種實驗工具已儼然成形,跟徐老師又另外準備了幾個關於編譯器最佳化的實驗,準備投稿國際研討會。

2019 年 8 月。
好消息與壞消息。壞消息是,國際研討會都沒有投稿成功,真是愧對徐老師跟中研院的吳老師及洪學長。好消息是,求職方面很順利,我前往面試的 MediaTek, Qualcomm, Synopsys, SiFive 都有獲得正面的評價。

最後。
碩士學涯便在投稿及求職完成後,辦理離校手續中安靜地結束。

如果我大四沒有跑去修成功大學電機系陳中和老師的計算機組織,楊老師或許就不會收我這個外系學生做研究助理。如果沒有楊老師的敦促,我閱讀的論文量可能寥寥可數。如果沒有跟 jserv 老師一起研究 Java 虛擬機器,徐老師可能不會收我這中途學生;如果我沒有去新竹晶心科技實習,論文研究不可能僅花費四個月完成。

填大學志願的時候完全沒有想過這麼多事情啊。
這真是太不可思議了。

2018年7月16日 星期一

QEMU 和 Dynamic Linker

最近因為在準備 COSCUP 的演講材料,需要在 QEMU 上面做一些實驗。實驗過程中,我準備了一個 Dynamically Linked Executables 要丟給 QEMU 去跑,但過程中遇到了一些問題。雖然應該只要改成 Static Linked 就能解決問題,不過就趁這個機會了解一下吧。

背景知識

作業系統背後有 Dynamic Linker。當我們在 Linux 執行一般編譯好的 ELF,其實 Loader 最先載入並跳轉的會是 Dynamic Linker,也就是 ELF Interpreter,去載入這個 ELF 所需要的 Shared Library 跟處理 Relocation 後,才會跳轉到 __libc_start_main。所以當 QEMU 執行一個 Dynamically Linked Executable 時,同樣也需要再去載入一個 Guest Dynamic Linker 去載入 ELF 所需要的 Shared Library,而這是指已經事先編譯在 Guest ISA 的 Shared Library。

一開始亂試卻成功了

使用 RISC-V GNU Toolchain 去編譯目標程式,如果沒有特別下 -static,就會是 Dynamically Linked。
$ $(riscv32-gnu-toolchain)/bin/riscv32-unknown-linux-gnu-gcc -o hello-rsv32 hello.c

接著使用 RISC-V QEMU 去執行 hello-rsv32
$ $(qemu-2.12.0)/build/bin/qemu-riscv32 hello-rsv32

然而卻出現以下訊息,找不到指定的 Dynamic Linker:
/lib/ld-linux-riscv32-ilp32d.so.1: No such file or directory

使用 file 觀察一下剛剛編譯好的 hello-rsv32:
hello-rsv32: ELF 32-bit LSB executable, UCB RISC-V, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-riscv32-ilp32d.so.1, for GNU/Linux 3.0.0, with debug_info, not stripped

表面上看起來 QEMU 會去 /lib 找這個給 linux-riscv32 用的 Dynamic Linker:ld-linux-riscv32-ilp32d.so.1。那我何不如在 Link Time 時指定正確的 Dynamic Linker 路徑給它就解決了。於是執行以下指令,可以指定 Dynamic Linker 給 linker:
$ $(riscv32-gnu-toolchain)/bin/riscv32-unknown-linux-gnu-gcc -Wl,--dynamic-linker $(riscv32-gnu-toolchain)/sysroot/lib/ld-linux-riscv32-ilp32d.so.1 -o hello-rsv32 hello.c

接下來跑在 QEMU 試試看,看起來 QEMU 成功找到 ld-linux-riscv32-ilp32d.so.1 了,結果現在換找不到 libc.so.6:
hello-rsv32: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory

回頭去查 QEMU help 看有沒有 Option 可以提供解決方法,於是還真的發現一個類似的選項 -L,我心裡想這應該是可以拿來指定 Shared Library 路徑的選項吧?
-L path QEMU_LD_PREFIX set the elf interpreter prefix to 'path'

於是執行以下指令,其中編譯給 RISC-V ISA 的 libc 會放在事先準備好的 RISC-V GNU Toolchain 裡的 sysroot/lib 資料夾:
$ $(qemu-2.12.0)/build/bin/qemu-riscv32 -L $(riscv32-gnu-toolchain)/sysroot/lib hello-rsv32

但還是找不到 libc.so.6:
hello-rsv32: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory

後來想到因為 Guest Dynamic Linker 最後也是被翻譯成 native code 執行,說不定會吃到 Host 的 LD_LIBRARY_PATH,於是執行下面指令將 libc.so.6 所在的資料夾餵進去,結果 QEMU 便執行成功了!
$ export LD_LIBRARY_PATH=$(riscv32-gnu-toolchain)/sysroot/lib

另外一個意外發現的方法是,將 -L 選項指定的 Path 往上翻一層,也能讓 QEMU 執行成功:
$ $(qemu-2.12.0)/build/bin/qemu-riscv32 -L $(riscv32-gnu-toolchain)/sysroot hello-rsv32


事情好像沒那麼簡單

先講結論,即使上面的方法在 Link Time 時指定 Dynamic Linker 或是直接用 LD_LIBRARY_PATH 將 Shared Library 餵給 QEMU 都可能行得通,但卻是忽略了 QEMU 提供 -L 這個選項的好處。而上述內容對 QEMU -L 的理解,也都是錯誤的。

回到剛剛 QEMU 搭配 -L 選項成功的時候,以為問題解決了。但印出 Dynamic Linker 的 DEBUG 訊息才發現事情並沒有這麼單純:
LD_DEBUG=libs $(qemu-2.12.0)/build/bin/qemu-riscv32 -L $(riscv32-gnu-toolchain)/sysroot hello-rsv32

下面的 DEBUG 訊息告訴我們,最後找到的 libc 竟然是 /lib/libc.so.6 而不是我們想要的 $(riscv32-gnu-toolchain)/sysroot/lib/libc.so.6?
     16708:     initialize program: /home/cwei/Workspace/qemu-2.12.0/build/bin
/qemu-riscv32
     16708:
     16708:
     16708:     transferring control: /home/cwei/Workspace/qemu-2.12.0/build/b
in/qemu-riscv32
     16708:
     16708:     find library=libc.so.6 [0]; searching
     16708:      search cache=/etc/ld.so.cache
     16708:      search path=/lib/tls:/lib:/usr/lib/tls:/usr/lib             (
system search path)
     16708:       trying file=/lib/tls/libc.so.6
     16708:       trying file=/lib/libc.so.6
     16708:
     16708:
     16708:     calling preinit: hello-rsv32
     16708:
     16708:
     16708:     calling init: /lib/libc.so.6

於是,我只好開始 trace QEMU 了。我在意的是,-L 選項下的路徑,究竟是存在哪裡,什麼時候又會用到。從 QEMU main function 開始追選項的 Parsing 的話,最後可以追到 -L 選項的路徑存在一個叫做 base 的 variable。而 base 最初一開始,是給一個在 linux-user/elfloader.c 中叫做 load_elf_interp 的 function 使用。


而這邊 follow_path 的作用是,它會去 base 所指到的路徑,抓出底下每一個檔案 (relative path) 去與 name (e.g. QEMU 目前所要找的 /lib/ld-linux-riscv32-ilp32d.so.1) 做比對。如果比對到了,便會將 base (也就是 -L 選項的路徑) 加到 name 的前面。於是 follow_path(base, name) 回傳的其實是 $(riscv32-gnu-toolchain)/sysroot/lib/ld-linux-riscv32-ilp32d.so.1,讓 QEMU 可以載入正確的 Guest Dynamic Linker。

但是剛剛印出來的 DEBUG 訊息的問題還沒有解決。於是我使用了 gdb 的 rwatch 功能可以觀察 QEMU 何時會再次存取 base。我一直 continue 到了 program 存取 "/lib/libc.so.6" 的時機,並使用 bt 觀察,並得到以下結果:QEMU 會在攔截 system call 時,會將 Guest Dynamic Linker 欲存取的 "/lib/libc.so.6" 再傳遞給剛剛談到的 path() ,於是回傳後變成了加上 prefix (-L 選項) 後的 path:$(riscv32-gnu-toolchain)/sysroot/lib/libc.so.6。而這也解釋了,為什麼使用 LD_DEBUG=libs 印出來的存取路徑是 "/lib/libc.so.6" 而不是 "$(riscv32-gnu-toolchain)/sysroot/lib/libc.so.6",因為 Guest Dynamic Linker 根本不知道它存取的 Shared Library 被 QEMU 偷偷調包了。

(gdb) bt 
#0 0x000055555569606b in path (name=0x7ffff627cd60 "/lib/libc.so.6") at util/path.c:173 
#1 0x0000555555618e0f in do_openat (cpu_env=0x555557bbc030, dirfd=-100, pathname=0x7ffff627cd60 "/lib/libc.so.6", flags=524288, mode=0) at /home/cwei/Workspace/qemu-2.12.0/linux-user/syscall.c:7713 
#2 0x000055555561981c in do_syscall (cpu_env=0x555557bbc030, num=56, arg1=-100, arg2=-8864, arg3=524288, arg4=0, arg5=0, arg6=4259840, arg7=0, arg8=0) at /home/cwei/Workspace/qemu-2.12.0/linux-user/syscall.c:7976 
#3 0x000055555560677f in cpu_loop (env=0x555557bbc030) at /home/cwei/Workspace/qemu-2.12.0/linux-user/main.c:3601 
#4 0x0000555555607f58 in main (argc=4, argv=0x7fffffffe368, envp=0x7fffffffe390) at /home/cwei/Workspace/qemu-2.12.0/linux-user/main.c:5147

小結

QEMU 的 -L 項讓我們其實可以不用去考慮編譯時是否要指定 Dynamic Linker,或是 LD_LIBRARY_PATH 要指到 libc.so.6 的地方。-L 選項巧妙的地方是在於,它會把 Dynamic Linker 的 sysroot 設定到我們想要的地方,便可以在 Guest Dynamic Linker 執行時置換它的路徑到指定之 sysroot 下。

所以,最後正確在 QEMU 執行 Dynamically Linked Executable 正確的方法,既不用在編譯時指 定Dynamic Linker,也不用另外設定 LD_LIBRARY_PATH:
$ $(qemu-2.12.0)/build/bin/qemu-riscv32 -L $(riscv32-gnu-toolchain)/sysroot hello-rsv32