tcc -O2會做什麼?
$ clang -c -O2n$ gcc -c -O2n$ tcc -c -O2n
就在專欄這邊記錄點文字吧。
TinyCC(Tiny C Compiler),簡稱TCC,它自身體積非常小,編譯/鏈接速度非常快,生成的代碼質量一般,可以自舉(自己可以編譯自己)。以前寫了點簡單的介紹,傳送門:Tiny C Compiler是個怎樣的編譯器?有人更新嗎? - RednaxelaFX的回答 - 知乎
之前也提到了,TCC之所以小而快,主要是因為它採用了典型的單趟編譯器(one-pass compiler)常見的「語法制導翻譯」方式來貫穿整個編譯器的實現。簡單來說,整個編譯器都融合在語法分析之中——由語法分析來驅動預處理與詞法分析,同時由語法分析來驅動語義分析與目標代碼生成;在完成語法分析的流程後,最終的目標代碼也就生成完畢了,中間不構造代碼的任何中間表示(IR),連AST也不構造。
這種做法在現代優化編譯器中已經很少見,但在一些極度精簡的編譯器或者腳本語言實現中的前端(例如Lua)中還很常見。這是因為這種架構常常需要把太多東西塞在一起,既不便於理解也不便於調試,同時也非常難擴展。
它也限制了信息流動的方向,使得某些需要後向數據流分析(backward data-flow analysis)的優化都無法實現,例如活躍變數分析(liveness analysis)/無用代碼刪除(dead code elimination)都做不了。
所以TCC實際實現了一些怎樣的優化呢?官網文檔里也有寫到,它實現了簡單的常量傳播與摺疊(都是前向數據流(forward data-flow));還實現了某些運算的強度減弱(strength reduction)優化,例如把2的冪方的常量乘法生成為左位移之類。也就這樣了。TCC所實現的這些優化都是不可以用參數來控制的。
但TCC確實支持-O<n>選項,傳-O0、-O1、-O2、-O3…-O10000給它,它都可以接受。那麼tcc -O<n>到底幹了什麼?
答案是:tcc -O,其中n大於0的時候,TCC會給被編譯的程序定義 __OPTIMIZE__ 宏。就跟 -D__OPTIMIZE__ 一樣。相關實現在這裡:libtcc.c
就這樣。嗯。-O1,-O2,-O10000都一樣。
TCC本身預處理/詞法分析/語法分析就寫得很緊湊,外加不用生成AST或者任何其它形式的IR,基本上同樣的源碼輸入進來,GCC還沒完成語法分析+GENERIC形式的AST構建,TCC就已經完成目標代碼生成,可以收工了…然後GCC在-O2下要得做些耗時間及內存的分析優化,那編譯速度自然是不能比咯——生成的代碼質量也同樣不能比。
就拿下面這個文件來演示一下吧:
int const_fold_literals() {n return 40 + 2; // both tcc and gcc optimized to 42n}nnint const_fold_literals_across(int x, int y) {n return 40 + x + y + 2; // tcc -O2: not optimizedn // gcc -O2: optimized to x + y + 42n}nnint cond_const_prop(int a) {n if (a) a = 0; // tcc -O2: not optimizedn // gcc -O2: optimized to return 0n return a;n}nnint main() {n const_fold_literals();n const_fold_literals_across(2, 3);n cond_const_prop(0);n return 0;n}n
嗯…
=========================================
這個 __OPTIMIZE__ 宏是一個GCC擴展里定義的宏,Clang也支持它。它的作用是讓某些庫函數可以根據程序編譯時是否優化來選擇不同的實現。例如說glibc的string.h:
#ifdef __OPTIMIZE__n__extern_always_inline char *nstrstr (char *__haystack, const char *__needle) __THROWn{n return __builtin_strstr (__haystack, __needle);n}nn__extern_always_inline const char *nstrstr (const char *__haystack, const char *__needle) __THROWn{n return __builtin_strstr (__haystack, __needle);n}n# endifn}n#elsenextern char *strstr (const char *__haystack, const char *__needle)n __THROW __attribute_pure__ __nonnull ((1, 2));n#endifn
這個宏其實自身並不影響編譯器的優化程度。不過當有些標準庫函數有編譯器intrinsics的時候,通過這個宏來保證庫里實現功能的代碼跟編譯器intrinsics要表達的語義完全一致,是件好事。
推薦閱讀:
※淺析Python解釋器的設計(二)
※[八卦] LLILC項目貌似掛了…
※寫在垠神前面: CHR3 語言
※不同編譯器是如何處理預編譯頭文件的?
※[八卦] Phoenix Compiler Infrastructure或許那個有望開源?